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