1 /*
2 * Copyright (c) 2016 GitHub, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 #include <algorithm>
17 #include <cstring>
18 #include <sstream>
19 #include <unordered_set>
20
21 #include <fcntl.h>
22 #include <sys/stat.h>
23 #include <sys/types.h>
24 #include <unistd.h>
25
26 #include "bcc_elf.h"
27 #include "bcc_proc.h"
28 #include "common.h"
29 #include "usdt.h"
30 #include "vendor/tinyformat.hpp"
31 #include "bcc_usdt.h"
32
33 namespace USDT {
34
Location(uint64_t addr,const std::string & bin_path,const char * arg_fmt)35 Location::Location(uint64_t addr, const std::string &bin_path, const char *arg_fmt)
36 : address_(addr),
37 bin_path_(bin_path) {
38
39 #ifdef __aarch64__
40 ArgumentParser_aarch64 parser(arg_fmt);
41 #elif __loongarch64
42 ArgumentParser_loongarch64 parser(arg_fmt);
43 #elif __powerpc64__
44 ArgumentParser_powerpc64 parser(arg_fmt);
45 #elif __s390x__
46 ArgumentParser_s390x parser(arg_fmt);
47 #else
48 ArgumentParser_x64 parser(arg_fmt);
49 #endif
50 while (!parser.done()) {
51 Argument arg;
52 if (!parser.parse(&arg))
53 continue;
54 arguments_.push_back(std::move(arg));
55 }
56 }
57
Probe(const char * bin_path,const char * provider,const char * name,uint64_t semaphore,uint64_t semaphore_offset,const optional<int> & pid,uint8_t mod_match_inode_only)58 Probe::Probe(const char *bin_path, const char *provider, const char *name,
59 uint64_t semaphore, uint64_t semaphore_offset,
60 const optional<int> &pid, uint8_t mod_match_inode_only)
61 : bin_path_(bin_path),
62 provider_(provider),
63 name_(name),
64 semaphore_(semaphore),
65 semaphore_offset_(semaphore_offset),
66 pid_(pid),
67 mod_match_inode_only_(mod_match_inode_only)
68 {}
69
in_shared_object(const std::string & bin_path)70 bool Probe::in_shared_object(const std::string &bin_path) {
71 if (object_type_map_.find(bin_path) == object_type_map_.end()) {
72 return (object_type_map_[bin_path] = bcc_elf_is_shared_obj(bin_path.c_str()));
73 }
74 return object_type_map_[bin_path];
75 }
76
resolve_global_address(uint64_t * global,const std::string & bin_path,const uint64_t addr)77 bool Probe::resolve_global_address(uint64_t *global, const std::string &bin_path,
78 const uint64_t addr) {
79 if (in_shared_object(bin_path)) {
80 return (pid_ &&
81 !bcc_resolve_global_addr(*pid_, bin_path.c_str(), addr, mod_match_inode_only_, global));
82 }
83
84 *global = addr;
85 return true;
86 }
87
add_to_semaphore(int16_t val)88 bool Probe::add_to_semaphore(int16_t val) {
89 assert(pid_);
90
91 if (!attached_semaphore_) {
92 uint64_t addr;
93 if (!resolve_global_address(&addr, bin_path_, semaphore_))
94 return false;
95 attached_semaphore_ = addr;
96 }
97
98 off_t address = static_cast<off_t>(attached_semaphore_.value());
99
100 std::string procmem = tfm::format("/proc/%d/mem", pid_.value());
101 int memfd = ::open(procmem.c_str(), O_RDWR);
102 if (memfd < 0)
103 return false;
104
105 int16_t original;
106
107 if (::lseek(memfd, address, SEEK_SET) < 0 ||
108 ::read(memfd, &original, 2) != 2) {
109 ::close(memfd);
110 return false;
111 }
112
113 original = original + val;
114
115 if (::lseek(memfd, address, SEEK_SET) < 0 ||
116 ::write(memfd, &original, 2) != 2) {
117 ::close(memfd);
118 return false;
119 }
120
121 ::close(memfd);
122 return true;
123 }
124
enable(const std::string & fn_name)125 bool Probe::enable(const std::string &fn_name) {
126 if (attached_to_)
127 return false;
128
129 if (need_enable()) {
130 if (!pid_)
131 return false;
132
133 if (!add_to_semaphore(+1))
134 return false;
135 }
136
137 attached_to_ = fn_name;
138 return true;
139 }
140
disable()141 bool Probe::disable() {
142 if (!attached_to_)
143 return false;
144
145 attached_to_ = nullopt;
146
147 if (need_enable()) {
148 assert(pid_);
149 return add_to_semaphore(-1);
150 }
151 return true;
152 }
153
largest_arg_type(size_t arg_n)154 const char *Probe::largest_arg_type(size_t arg_n) {
155 Argument *largest = nullptr;
156 for (Location &location : locations_) {
157 Argument *candidate = &location.arguments_[arg_n];
158 if (!largest ||
159 std::abs(candidate->arg_size()) > std::abs(largest->arg_size()))
160 largest = candidate;
161 }
162
163 assert(largest);
164 return largest->ctype_name();
165 }
166
usdt_getarg(std::ostream & stream)167 bool Probe::usdt_getarg(std::ostream &stream) {
168 if (!attached_to_ || attached_to_->empty())
169 return false;
170
171 return usdt_getarg(stream, attached_to_.value());
172 }
173
usdt_getarg(std::ostream & stream,const std::string & probe_func)174 bool Probe::usdt_getarg(std::ostream &stream, const std::string& probe_func) {
175 const size_t arg_count = locations_[0].arguments_.size();
176
177 if (arg_count == 0)
178 return true;
179
180 uint64_t page_size = sysconf(_SC_PAGESIZE);
181 std::unordered_set<int> page_offsets;
182 for (Location &location : locations_)
183 page_offsets.insert(location.address_ % page_size);
184
185 for (size_t arg_n = 0; arg_n < arg_count; ++arg_n) {
186 std::string ctype = largest_arg_type(arg_n);
187 std::string cptr = tfm::format("*((%s *)dest)", ctype);
188
189 tfm::format(stream,
190 "static __always_inline int _bpf_readarg_%s_%d("
191 "struct pt_regs *ctx, void *dest, size_t len) {\n"
192 " if (len != sizeof(%s)) return -1;\n",
193 probe_func, arg_n + 1, ctype);
194
195 if (locations_.size() == 1) {
196 Location &location = locations_.front();
197 stream << " ";
198 if (!location.arguments_[arg_n].assign_to_local(stream, cptr, location.bin_path_,
199 pid_))
200 return false;
201 stream << "\n return 0;\n}\n";
202 } else {
203 if (page_offsets.size() == locations_.size())
204 tfm::format(stream, " switch (PT_REGS_IP(ctx) %% 0x%xULL) {\n", page_size);
205 else
206 stream << " switch (PT_REGS_IP(ctx)) {\n";
207 for (Location &location : locations_) {
208 if (page_offsets.size() == locations_.size()) {
209 tfm::format(stream, " case 0x%xULL: ", location.address_ % page_size);
210 } else {
211 uint64_t global_address;
212
213 if (!resolve_global_address(&global_address, location.bin_path_,
214 location.address_))
215 return false;
216
217 tfm::format(stream, " case 0x%xULL: ", global_address);
218 }
219 if (!location.arguments_[arg_n].assign_to_local(stream, cptr, location.bin_path_,
220 pid_))
221 return false;
222
223 stream << " return 0;\n";
224 }
225 stream << " }\n";
226 stream << " return -1;\n}\n";
227 }
228 }
229 return true;
230 }
231
add_location(uint64_t addr,const std::string & bin_path,const char * fmt)232 void Probe::add_location(uint64_t addr, const std::string &bin_path, const char *fmt) {
233 locations_.emplace_back(addr, bin_path, fmt);
234 }
235
finalize_locations()236 void Probe::finalize_locations() {
237 // The following comparator needs to establish a strict weak ordering relation. Such
238 // that when x < y == true, y < x == false. Otherwise it leads to undefined behavior.
239 // To guarantee this, it uses std::tie which allows the lambda to have a lexicographical
240 // comparison and hence, guarantee the strict weak ordering.
241 std::sort(locations_.begin(), locations_.end(),
242 [](const Location &a, const Location &b) {
243 return std::tie(a.bin_path_, a.address_) < std::tie(b.bin_path_, b.address_);
244 });
245 auto last = std::unique(locations_.begin(), locations_.end(),
246 [](const Location &a, const Location &b) {
247 return a.bin_path_ == b.bin_path_ && a.address_ == b.address_;
248 });
249 locations_.erase(last, locations_.end());
250 }
251
_each_probe(const char * binpath,const struct bcc_elf_usdt * probe,void * p)252 void Context::_each_probe(const char *binpath, const struct bcc_elf_usdt *probe,
253 void *p) {
254 Context *ctx = static_cast<Context *>(p);
255 ctx->add_probe(binpath, probe);
256 }
257
_each_module(mod_info * mod,int enter_ns,void * p)258 int Context::_each_module(mod_info *mod, int enter_ns, void *p) {
259 Context *ctx = static_cast<Context *>(p);
260
261 std::string path = mod->name;
262 if (ctx->pid_ && *ctx->pid_ != -1 && enter_ns) {
263 path = tfm::format("/proc/%d/root%s", *ctx->pid_, path);
264 }
265
266 // Modules may be reported multiple times if they contain more than one
267 // executable region. We are going to parse the ELF on disk anyway, so we
268 // don't need these duplicates.
269 if (ctx->modules_.insert(path).second /*inserted new?*/) {
270 bcc_elf_foreach_usdt(path.c_str(), _each_probe, p);
271 }
272 return 0;
273 }
274
add_probe(const char * binpath,const struct bcc_elf_usdt * probe)275 void Context::add_probe(const char *binpath, const struct bcc_elf_usdt *probe) {
276 for (auto &p : probes_) {
277 if (p->provider_ == probe->provider && p->name_ == probe->name) {
278 p->add_location(probe->pc, binpath, probe->arg_fmt);
279 return;
280 }
281 }
282
283 probes_.emplace_back(
284 new Probe(binpath, probe->provider, probe->name, probe->semaphore,
285 probe->semaphore_offset, pid_, mod_match_inode_only_)
286 );
287 probes_.back()->add_location(probe->pc, binpath, probe->arg_fmt);
288 }
289
resolve_bin_path(const std::string & bin_path)290 std::string Context::resolve_bin_path(const std::string &bin_path) {
291 std::string result;
292
293 if (char *which = bcc_procutils_which(bin_path.c_str())) {
294 result = which;
295 ::free(which);
296 } else if (char *which_so = bcc_procutils_which_so(bin_path.c_str(), 0)) {
297 result = which_so;
298 ::free(which_so);
299 }
300
301 if (!result.empty() && pid_ && *pid_ != -1 && result.find("/proc") != 0) {
302 result = tfm::format("/proc/%d/root%s", *pid_, result);
303 }
304
305 return result;
306 }
307
get(const std::string & probe_name)308 Probe *Context::get(const std::string &probe_name) {
309 for (auto &p : probes_) {
310 if (p->name_ == probe_name)
311 return p.get();
312 }
313 return nullptr;
314 }
315
get(const std::string & provider_name,const std::string & probe_name)316 Probe *Context::get(const std::string &provider_name,
317 const std::string &probe_name) {
318 for (auto &p : probes_) {
319 if (p->provider_ == provider_name && p->name_ == probe_name)
320 return p.get();
321 }
322 return nullptr;
323 }
324
enable_probe(const std::string & probe_name,const std::string & fn_name)325 bool Context::enable_probe(const std::string &probe_name,
326 const std::string &fn_name) {
327 return enable_probe("", probe_name, fn_name);
328 }
329
get_checked(const std::string & provider_name,const std::string & probe_name)330 Probe *Context::get_checked(const std::string &provider_name,
331 const std::string &probe_name) {
332 if (pid_stat_ && pid_stat_->is_stale())
333 return nullptr;
334
335 Probe *found_probe = nullptr;
336 for (auto &p : probes_) {
337 if (p->name_ == probe_name &&
338 (provider_name.empty() || p->provider() == provider_name)) {
339 if (found_probe != nullptr) {
340 fprintf(stderr, "Two same-name probes (%s) but different providers\n",
341 probe_name.c_str());
342 return nullptr;
343 }
344 found_probe = p.get();
345 }
346 }
347
348 return found_probe;
349 }
350
enable_probe(const std::string & provider_name,const std::string & probe_name,const std::string & fn_name)351 bool Context::enable_probe(const std::string &provider_name,
352 const std::string &probe_name,
353 const std::string &fn_name) {
354 Probe *found_probe = get_checked(provider_name, probe_name);
355
356 if (found_probe != nullptr)
357 return found_probe->enable(fn_name);
358
359 return false;
360 }
361
each(each_cb callback)362 void Context::each(each_cb callback) {
363 for (const auto &probe : probes_) {
364 struct bcc_usdt info = {0};
365 info.provider = probe->provider().c_str();
366 info.bin_path = probe->bin_path().c_str();
367 info.name = probe->name().c_str();
368 info.semaphore = probe->semaphore();
369 info.semaphore_offset = probe->semaphore_offset();
370 info.num_locations = probe->num_locations();
371 info.num_arguments = probe->num_arguments();
372 callback(&info);
373 }
374 }
375
addsem_probe(const std::string & provider_name,const std::string & probe_name,const std::string & fn_name,int16_t val)376 bool Context::addsem_probe(const std::string &provider_name,
377 const std::string &probe_name,
378 const std::string &fn_name,
379 int16_t val) {
380 Probe *found_probe = get_checked(provider_name, probe_name);
381
382 if (found_probe != nullptr) {
383 if (found_probe->need_enable())
384 return found_probe->add_to_semaphore(val);
385
386 return true;
387 }
388
389 return false;
390 }
391
each_uprobe(each_uprobe_cb callback)392 void Context::each_uprobe(each_uprobe_cb callback) {
393 for (auto &p : probes_) {
394 if (!p->enabled())
395 continue;
396
397 for (Location &loc : p->locations_) {
398 callback(loc.bin_path_.c_str(), p->attached_to_->c_str(), loc.address_,
399 pid_.value_or(-1));
400 }
401 }
402 }
403
Context(const std::string & bin_path,uint8_t mod_match_inode_only)404 Context::Context(const std::string &bin_path, uint8_t mod_match_inode_only)
405 : loaded_(false), mod_match_inode_only_(mod_match_inode_only) {
406 std::string full_path = resolve_bin_path(bin_path);
407 if (!full_path.empty()) {
408 if (bcc_elf_foreach_usdt(full_path.c_str(), _each_probe, this) == 0) {
409 cmd_bin_path_ = full_path;
410 loaded_ = true;
411 }
412 }
413 for (const auto &probe : probes_)
414 probe->finalize_locations();
415 }
416
Context(int pid,uint8_t mod_match_inode_only)417 Context::Context(int pid, uint8_t mod_match_inode_only)
418 : pid_(pid), pid_stat_(pid), loaded_(false),
419 mod_match_inode_only_(mod_match_inode_only) {
420 if (bcc_procutils_each_module(pid, _each_module, this) == 0) {
421 cmd_bin_path_ = ebpf::get_pid_exe(pid);
422 if (cmd_bin_path_.empty())
423 return;
424
425 loaded_ = true;
426 }
427 for (const auto &probe : probes_)
428 probe->finalize_locations();
429 }
430
Context(int pid,const std::string & bin_path,uint8_t mod_match_inode_only)431 Context::Context(int pid, const std::string &bin_path,
432 uint8_t mod_match_inode_only)
433 : pid_(pid), pid_stat_(pid), loaded_(false),
434 mod_match_inode_only_(mod_match_inode_only) {
435 std::string full_path = resolve_bin_path(bin_path);
436 if (!full_path.empty()) {
437 int res = bcc_elf_foreach_usdt(full_path.c_str(), _each_probe, this);
438 if (res == 0) {
439 cmd_bin_path_ = ebpf::get_pid_exe(pid);
440 if (cmd_bin_path_.empty())
441 return;
442 loaded_ = true;
443 }
444 }
445 for (const auto &probe : probes_)
446 probe->finalize_locations();
447 }
448
~Context()449 Context::~Context() {
450 if (pid_stat_ && !pid_stat_->is_stale()) {
451 for (auto &p : probes_) p->disable();
452 }
453 }
454 }
455
456 extern "C" {
457
bcc_usdt_new_frompid(int pid,const char * path)458 void *bcc_usdt_new_frompid(int pid, const char *path) {
459 USDT::Context *ctx;
460
461 if (!path) {
462 ctx = new USDT::Context(pid);
463 } else {
464 struct stat buffer;
465 if (strlen(path) >= 1 && path[0] != '/') {
466 fprintf(stderr, "HINT: Binary path %s should be absolute.\n\n", path);
467 return nullptr;
468 } else if (stat(path, &buffer) == -1) {
469 fprintf(stderr, "HINT: Specified binary %s doesn't exist.\n\n", path);
470 return nullptr;
471 }
472 ctx = new USDT::Context(pid, path);
473 }
474 if (!ctx->loaded()) {
475 delete ctx;
476 return nullptr;
477 }
478 return static_cast<void *>(ctx);
479 }
480
bcc_usdt_new_frompath(const char * path)481 void *bcc_usdt_new_frompath(const char *path) {
482 USDT::Context *ctx = new USDT::Context(path);
483 if (!ctx->loaded()) {
484 delete ctx;
485 return nullptr;
486 }
487 return static_cast<void *>(ctx);
488 }
489
bcc_usdt_close(void * usdt)490 void bcc_usdt_close(void *usdt) {
491 if (usdt) {
492 USDT::Context *ctx = static_cast<USDT::Context *>(usdt);
493 delete ctx;
494 }
495 }
496
bcc_usdt_enable_probe(void * usdt,const char * probe_name,const char * fn_name)497 int bcc_usdt_enable_probe(void *usdt, const char *probe_name,
498 const char *fn_name) {
499 USDT::Context *ctx = static_cast<USDT::Context *>(usdt);
500 return ctx->enable_probe(probe_name, fn_name) ? 0 : -1;
501 }
502
bcc_usdt_addsem_probe(void * usdt,const char * probe_name,const char * fn_name,int16_t val)503 int bcc_usdt_addsem_probe(void *usdt, const char *probe_name,
504 const char *fn_name, int16_t val) {
505 USDT::Context *ctx = static_cast<USDT::Context *>(usdt);
506 return ctx->addsem_probe("", probe_name, fn_name, val) ? 0 : -1;
507 }
508
bcc_usdt_enable_fully_specified_probe(void * usdt,const char * provider_name,const char * probe_name,const char * fn_name)509 int bcc_usdt_enable_fully_specified_probe(void *usdt, const char *provider_name,
510 const char *probe_name,
511 const char *fn_name) {
512 USDT::Context *ctx = static_cast<USDT::Context *>(usdt);
513 return ctx->enable_probe(provider_name, probe_name, fn_name) ? 0 : -1;
514 }
515
bcc_usdt_addsem_fully_specified_probe(void * usdt,const char * provider_name,const char * probe_name,const char * fn_name,int16_t val)516 int bcc_usdt_addsem_fully_specified_probe(void *usdt, const char *provider_name,
517 const char *probe_name,
518 const char *fn_name, int16_t val) {
519 USDT::Context *ctx = static_cast<USDT::Context *>(usdt);
520 return ctx->addsem_probe(provider_name, probe_name, fn_name, val) ? 0 : -1;
521 }
522
bcc_usdt_genargs(void ** usdt_array,int len)523 const char *bcc_usdt_genargs(void **usdt_array, int len) {
524 static std::string storage_;
525 std::ostringstream stream;
526
527 if (!len)
528 return "";
529
530 stream << USDT::USDT_PROGRAM_HEADER;
531 // Generate genargs codes for an array of USDT Contexts.
532 //
533 // Each cmd_bin_path + probe_provider + probe_name
534 // uniquely identifies a probe.
535 std::unordered_set<std::string> generated_probes;
536 for (int i = 0; i < len; i++) {
537 USDT::Context *ctx = static_cast<USDT::Context *>(usdt_array[i]);
538
539 for (size_t j = 0; j < ctx->num_probes(); j++) {
540 USDT::Probe *p = ctx->get(j);
541 if (p->enabled()) {
542 std::string key = ctx->cmd_bin_path() + "*" + p->provider() + "*" + p->name();
543 if (generated_probes.find(key) != generated_probes.end())
544 continue;
545 if (!p->usdt_getarg(stream))
546 return nullptr;
547 generated_probes.insert(key);
548 }
549 }
550 }
551
552 storage_ = stream.str();
553 return storage_.c_str();
554 }
555
bcc_usdt_get_probe_argctype(void * ctx,const char * probe_name,const int arg_index)556 const char *bcc_usdt_get_probe_argctype(
557 void *ctx, const char* probe_name, const int arg_index
558 ) {
559 USDT::Probe *p = static_cast<USDT::Context *>(ctx)->get(probe_name);
560 if (p)
561 return p->get_arg_ctype_name(arg_index);
562 return "";
563 }
564
bcc_usdt_get_fully_specified_probe_argctype(void * ctx,const char * provider_name,const char * probe_name,const int arg_index)565 const char *bcc_usdt_get_fully_specified_probe_argctype(
566 void *ctx, const char* provider_name, const char* probe_name, const int arg_index
567 ) {
568 USDT::Probe *p = static_cast<USDT::Context *>(ctx)->get(provider_name, probe_name);
569 if (p)
570 return p->get_arg_ctype_name(arg_index);
571 return "";
572 }
573
bcc_usdt_foreach(void * usdt,bcc_usdt_cb callback)574 void bcc_usdt_foreach(void *usdt, bcc_usdt_cb callback) {
575 USDT::Context *ctx = static_cast<USDT::Context *>(usdt);
576 ctx->each(callback);
577 }
578
bcc_usdt_get_location(void * usdt,const char * provider_name,const char * probe_name,int index,struct bcc_usdt_location * location)579 int bcc_usdt_get_location(void *usdt, const char *provider_name,
580 const char *probe_name,
581 int index, struct bcc_usdt_location *location) {
582 USDT::Context *ctx = static_cast<USDT::Context *>(usdt);
583 USDT::Probe *probe = ctx->get(provider_name, probe_name);
584 if (!probe)
585 return -1;
586 if (index < 0 || (size_t)index >= probe->num_locations())
587 return -1;
588 location->address = probe->address(index);
589 location->bin_path = probe->location_bin_path(index);
590 return 0;
591 }
592
bcc_usdt_get_argument(void * usdt,const char * provider_name,const char * probe_name,int location_index,int argument_index,struct bcc_usdt_argument * argument)593 int bcc_usdt_get_argument(void *usdt, const char *provider_name,
594 const char *probe_name,
595 int location_index, int argument_index,
596 struct bcc_usdt_argument *argument) {
597 USDT::Context *ctx = static_cast<USDT::Context *>(usdt);
598 USDT::Probe *probe = ctx->get(provider_name, probe_name);
599 if (!probe)
600 return -1;
601 if (argument_index < 0 || (size_t)argument_index >= probe->num_arguments())
602 return -1;
603 if (location_index < 0 || (size_t)location_index >= probe->num_locations())
604 return -1;
605 auto const &location = probe->location(location_index);
606 auto const &arg = location.arguments_[argument_index];
607 argument->size = arg.arg_size();
608 argument->valid = BCC_USDT_ARGUMENT_NONE;
609 if (arg.constant()) {
610 argument->valid |= BCC_USDT_ARGUMENT_CONSTANT;
611 argument->constant = *(arg.constant());
612 }
613 if (arg.deref_offset()) {
614 argument->valid |= BCC_USDT_ARGUMENT_DEREF_OFFSET;
615 argument->deref_offset = *(arg.deref_offset());
616 }
617 if (arg.deref_ident()) {
618 argument->valid |= BCC_USDT_ARGUMENT_DEREF_IDENT;
619 argument->deref_ident = arg.deref_ident()->c_str();
620 }
621 if (arg.base_register_name()) {
622 argument->valid |= BCC_USDT_ARGUMENT_BASE_REGISTER_NAME;
623 argument->base_register_name = arg.base_register_name()->c_str();
624 }
625 if (arg.index_register_name()) {
626 argument->valid |= BCC_USDT_ARGUMENT_INDEX_REGISTER_NAME;
627 argument->index_register_name = arg.index_register_name()->c_str();
628 }
629 if (arg.scale()) {
630 argument->valid |= BCC_USDT_ARGUMENT_SCALE;
631 argument->scale = *(arg.scale());
632 }
633 return 0;
634 }
635
bcc_usdt_foreach_uprobe(void * usdt,bcc_usdt_uprobe_cb callback)636 void bcc_usdt_foreach_uprobe(void *usdt, bcc_usdt_uprobe_cb callback) {
637 USDT::Context *ctx = static_cast<USDT::Context *>(usdt);
638 ctx->each_uprobe(callback);
639 }
640 }
641