From: Brenden Blanco Date: Mon, 26 Jun 2017 20:37:34 +0000 (-0700) Subject: src/cc: cmake file cleanup and split usdt into subdir X-Git-Tag: submit/tizen_4.0/20171018.110122~23 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=25f3ccee72f50f14fd51166a9839f86570be9f72;p=platform%2Fupstream%2Fbcc.git src/cc: cmake file cleanup and split usdt into subdir Move two usdt related files to a new subdirectory and link them into the final product. Introduce a cmake option to disable that feature (turning it off currently fails to compile). Move some of the cmake flag computation into a helper cmake file and include that rather than inlining it. Signed-off-by: Brenden Blanco --- diff --git a/cmake/clang_libs.cmake b/cmake/clang_libs.cmake new file mode 100644 index 00000000..907b784f --- /dev/null +++ b/cmake/clang_libs.cmake @@ -0,0 +1,42 @@ +set(llvm_raw_libs bitwriter bpfcodegen irreader linker + mcjit objcarcopts option passes nativecodegen lto) +list(FIND LLVM_AVAILABLE_LIBS "LLVMCoverage" _llvm_coverage) +if (${_llvm_coverage} GREATER -1) + list(APPEND llvm_raw_libs coverage) +endif() +list(FIND LLVM_AVAILABLE_LIBS "LLVMCoroutines" _llvm_coroutines) +if (${_llvm_coroutines} GREATER -1) + list(APPEND llvm_raw_libs coroutines) +endif() +llvm_map_components_to_libnames(_llvm_libs ${llvm_raw_libs}) +llvm_expand_dependencies(llvm_libs ${_llvm_libs}) + +# order is important +set(clang_libs + ${libclangFrontend} + ${libclangSerialization} + ${libclangDriver} + ${libclangParse} + ${libclangSema} + ${libclangCodeGen} + ${libclangAnalysis} + ${libclangRewrite} + ${libclangEdit} + ${libclangAST} + ${libclangLex} + ${libclangBasic}) + +# prune unused llvm static library stuff when linking into the new .so +set(_exclude_flags) +foreach(_lib ${clang_libs}) + get_filename_component(_lib ${_lib} NAME) + set(_exclude_flags "${_exclude_flags} -Wl,--exclude-libs=${_lib}") +endforeach(_lib) +set(clang_lib_exclude_flags "${_exclude_flags}") + +set(_exclude_flags) +foreach(_lib ${llvm_libs}) + get_filename_component(_lib ${_lib} NAME) + set(_exclude_flags "${_exclude_flags} -Wl,--exclude-libs=lib${_lib}.a") +endforeach(_lib) +set(llvm_lib_exclude_flags "${_exclude_flags}") diff --git a/cmake/static_libstdc++.cmake b/cmake/static_libstdc++.cmake new file mode 100644 index 00000000..3c8ac179 --- /dev/null +++ b/cmake/static_libstdc++.cmake @@ -0,0 +1,15 @@ +# only turn on static-libstdc++ if also linking statically against clang +string(REGEX MATCH ".*[.]a$" LIBCLANG_ISSTATIC "${libclangBasic}") +# if gcc 4.9 or higher is used, static libstdc++ is a good option +if (CMAKE_COMPILER_IS_GNUCC AND LIBCLANG_ISSTATIC) + execute_process(COMMAND ${CMAKE_C_COMPILER} -dumpversion OUTPUT_VARIABLE GCC_VERSION) + if (GCC_VERSION VERSION_GREATER 4.9 OR GCC_VERSION VERSION_EQUAL 4.9) + execute_process(COMMAND ${CMAKE_C_COMPILER} -print-libgcc-file-name OUTPUT_VARIABLE GCC_LIB) + get_filename_component(GCC_DIR "${GCC_LIB}" DIRECTORY) + find_library(GCC_LIBSTDCPP libstdc++.a PATHS "${GCC_DIR}" NO_DEFAULT_PATH) + if (GCC_LIBSTDCPP) + message(STATUS "Using static-libstdc++") + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -static-libstdc++") + endif() + endif() +endif() diff --git a/src/cc/CMakeLists.txt b/src/cc/CMakeLists.txt index 5c4cfa5a..bc25a8db 100644 --- a/src/cc/CMakeLists.txt +++ b/src/cc/CMakeLists.txt @@ -17,21 +17,7 @@ configure_file(libbcc.pc.in ${CMAKE_CURRENT_BINARY_DIR}/libbcc.pc @ONLY) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIC") -# only turn on static-libstdc++ if also linking statically against clang -string(REGEX MATCH ".*[.]a$" LIBCLANG_ISSTATIC "${libclangBasic}") -# if gcc 4.9 or higher is used, static libstdc++ is a good option -if (CMAKE_COMPILER_IS_GNUCC AND LIBCLANG_ISSTATIC) - execute_process(COMMAND ${CMAKE_C_COMPILER} -dumpversion OUTPUT_VARIABLE GCC_VERSION) - if (GCC_VERSION VERSION_GREATER 4.9 OR GCC_VERSION VERSION_EQUAL 4.9) - execute_process(COMMAND ${CMAKE_C_COMPILER} -print-libgcc-file-name OUTPUT_VARIABLE GCC_LIB) - get_filename_component(GCC_DIR "${GCC_LIB}" DIRECTORY) - find_library(GCC_LIBSTDCPP libstdc++.a PATHS "${GCC_DIR}" NO_DEFAULT_PATH) - if (GCC_LIBSTDCPP) - message(STATUS "Using static-libstdc++") - set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -static-libstdc++") - endif() - endif() -endif() +include(static_libstdc++) add_library(bpf-static STATIC libbpf.c perf_reader.c) set_target_properties(bpf-static PROPERTIES OUTPUT_NAME bpf) @@ -40,8 +26,8 @@ set_target_properties(bpf-shared PROPERTIES OUTPUT_NAME bpf) add_library(bcc-shared SHARED bpf_common.cc bpf_module.cc table_storage.cc shared_table.cc bpffs_table.cc json_map_decl_visitor.cc exported_files.cc - bcc_elf.c bcc_perf_map.c bcc_proc.c bcc_syms.cc ns_guard.cc usdt_args.cc - usdt.cc common.cc BPF.cc BPFTable.cc) + bcc_elf.c bcc_perf_map.c bcc_proc.c bcc_syms.cc ns_guard.cc common.cc BPF.cc + BPFTable.cc) set_target_properties(bcc-shared PROPERTIES VERSION ${REVISION_LAST} SOVERSION 0) set_target_properties(bcc-shared PROPERTIES OUTPUT_NAME bcc) @@ -49,42 +35,25 @@ add_library(bcc-loader-static STATIC bcc_elf.c bcc_perf_map.c bcc_proc.c bcc_syms.cc ns_guard.cc) add_library(bcc-static STATIC bpf_common.cc bpf_module.cc shared_table.cc bpffs_table.cc json_map_decl_visitor.cc table_storage.cc exported_files.cc - usdt_args.cc usdt.cc common.cc BPF.cc BPFTable.cc) + common.cc BPF.cc BPFTable.cc) set_target_properties(bcc-static PROPERTIES OUTPUT_NAME bcc) -set(llvm_raw_libs bitwriter bpfcodegen irreader linker - mcjit objcarcopts option passes nativecodegen lto) -list(FIND LLVM_AVAILABLE_LIBS "LLVMCoverage" _llvm_coverage) -if (${_llvm_coverage} GREATER -1) - list(APPEND llvm_raw_libs coverage) -endif() -list(FIND LLVM_AVAILABLE_LIBS "LLVMCoroutines" _llvm_coroutines) -if (${_llvm_coroutines} GREATER -1) - list(APPEND llvm_raw_libs coroutines) -endif() -llvm_map_components_to_libnames(llvm_libs ${llvm_raw_libs}) -llvm_expand_dependencies(expanded_libs ${llvm_libs}) +include(clang_libs) +set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${clang_lib_exclude_flags}") +set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${llvm_lib_exclude_flags}") -# order is important -set(clang_libs ${libclangFrontend} ${libclangSerialization} ${libclangDriver} ${libclangParse} - ${libclangSema} ${libclangCodeGen} ${libclangAnalysis} ${libclangRewrite} ${libclangEdit} - ${libclangAST} ${libclangLex} ${libclangBasic}) +set(bcc_common_libs b_frontend clang_frontend bpf-static + ${clang_libs} ${llvm_libs} ${LIBELF_LIBRARIES}) -# prune unused llvm static library stuff when linking into the new .so -foreach(lib ${clang_libs}) - get_filename_component(lib ${lib} NAME) - set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--exclude-libs=${lib}") -endforeach(lib) -foreach(lib ${expanded_libs}) - get_filename_component(lib ${lib} NAME) - set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--exclude-libs=lib${lib}.a") -endforeach(lib) +option(ENABLE_USDT "Enable User-level Statically Defined Tracing" ON) +if(ENABLE_USDT) + add_subdirectory(usdt) + list(APPEND bcc_common_libs usdt-static) +endif() # Link against LLVM libraries -target_link_libraries(bcc-shared b_frontend clang_frontend bpf-static - ${clang_libs} ${expanded_libs} ${LIBELF_LIBRARIES}) -target_link_libraries(bcc-static b_frontend clang_frontend bcc-loader-static - bpf-static ${clang_libs} ${expanded_libs} ${LIBELF_LIBRARIES}) +target_link_libraries(bcc-shared ${bcc_common_libs}) +target_link_libraries(bcc-static ${bcc_common_libs} bcc-loader-static) install(TARGETS bcc-shared LIBRARY COMPONENT libbcc DESTINATION ${CMAKE_INSTALL_LIBDIR}) diff --git a/src/cc/usdt.cc b/src/cc/usdt.cc deleted file mode 100644 index ccb7caab..00000000 --- a/src/cc/usdt.cc +++ /dev/null @@ -1,460 +0,0 @@ -/* - * Copyright (c) 2016 GitHub, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include -#include -#include - -#include -#include -#include - -#include "bcc_elf.h" -#include "bcc_proc.h" -#include "usdt.h" -#include "vendor/tinyformat.hpp" -#include "bcc_usdt.h" - -namespace USDT { - -Location::Location(uint64_t addr, const char *arg_fmt) : address_(addr) { - ArgumentParser_x64 parser(arg_fmt); - while (!parser.done()) { - Argument arg; - if (!parser.parse(&arg)) - continue; - arguments_.push_back(std::move(arg)); - } -} - -Probe::Probe(const char *bin_path, const char *provider, const char *name, - uint64_t semaphore, const optional &pid, ProcMountNS *ns) - : bin_path_(bin_path), - provider_(provider), - name_(name), - semaphore_(semaphore), - pid_(pid), - mount_ns_(ns) {} - -bool Probe::in_shared_object() { - if (!in_shared_object_) { - ProcMountNSGuard g(mount_ns_); - in_shared_object_ = bcc_elf_is_shared_obj(bin_path_.c_str()); - } - return in_shared_object_.value(); -} - -bool Probe::resolve_global_address(uint64_t *global, const uint64_t addr) { - if (in_shared_object()) { - return (pid_ && - !bcc_resolve_global_addr(*pid_, bin_path_.c_str(), addr, global)); - } - - *global = addr; - return true; -} - -bool Probe::add_to_semaphore(int16_t val) { - assert(pid_); - - if (!attached_semaphore_) { - uint64_t addr; - if (!resolve_global_address(&addr, semaphore_)) - return false; - attached_semaphore_ = addr; - } - - off_t address = static_cast(attached_semaphore_.value()); - - std::string procmem = tfm::format("/proc/%d/mem", pid_.value()); - int memfd = ::open(procmem.c_str(), O_RDWR); - if (memfd < 0) - return false; - - int16_t original; - - if (::lseek(memfd, address, SEEK_SET) < 0 || - ::read(memfd, &original, 2) != 2) { - ::close(memfd); - return false; - } - - original = original + val; - - if (::lseek(memfd, address, SEEK_SET) < 0 || - ::write(memfd, &original, 2) != 2) { - ::close(memfd); - return false; - } - - ::close(memfd); - return true; -} - -bool Probe::enable(const std::string &fn_name) { - if (attached_to_) - return false; - - if (need_enable()) { - if (!pid_) - return false; - - if (!add_to_semaphore(+1)) - return false; - } - - attached_to_ = fn_name; - return true; -} - -bool Probe::disable() { - if (!attached_to_) - return false; - - attached_to_ = nullopt; - - if (need_enable()) { - assert(pid_); - return add_to_semaphore(-1); - } - return true; -} - -std::string Probe::largest_arg_type(size_t arg_n) { - Argument *largest = nullptr; - for (Location &location : locations_) { - Argument *candidate = &location.arguments_[arg_n]; - if (!largest || - std::abs(candidate->arg_size()) > std::abs(largest->arg_size())) - largest = candidate; - } - - assert(largest); - return largest->ctype(); -} - -bool Probe::usdt_getarg(std::ostream &stream) { - const size_t arg_count = locations_[0].arguments_.size(); - - if (!attached_to_) - return false; - - if (arg_count == 0) - return true; - - for (size_t arg_n = 0; arg_n < arg_count; ++arg_n) { - std::string ctype = largest_arg_type(arg_n); - std::string cptr = tfm::format("*((%s *)dest)", ctype); - - tfm::format(stream, - "static __always_inline int _bpf_readarg_%s_%d(" - "struct pt_regs *ctx, void *dest, size_t len) {\n" - " if (len != sizeof(%s)) return -1;\n", - attached_to_.value(), arg_n + 1, ctype); - - if (locations_.size() == 1) { - Location &location = locations_.front(); - stream << " "; - if (!location.arguments_[arg_n].assign_to_local(stream, cptr, bin_path_, - pid_)) - return false; - stream << "\n return 0;\n}\n"; - } else { - stream << " switch(ctx->ip) {\n"; - for (Location &location : locations_) { - uint64_t global_address; - - if (!resolve_global_address(&global_address, location.address_)) - return false; - - tfm::format(stream, " case 0x%xULL: ", global_address); - if (!location.arguments_[arg_n].assign_to_local(stream, cptr, bin_path_, - pid_)) - return false; - - stream << " return 0;\n"; - } - stream << " }\n"; - stream << " return -1;\n}\n"; - } - } - return true; -} - -void Probe::add_location(uint64_t addr, const char *fmt) { - locations_.emplace_back(addr, fmt); -} - -void Context::_each_probe(const char *binpath, const struct bcc_elf_usdt *probe, - void *p) { - Context *ctx = static_cast(p); - ctx->add_probe(binpath, probe); -} - -int Context::_each_module(const char *modpath, uint64_t, uint64_t, bool, void *p) { - Context *ctx = static_cast(p); - // Modules may be reported multiple times if they contain more than one - // executable region. We are going to parse the ELF on disk anyway, so we - // don't need these duplicates. - if (ctx->modules_.insert(modpath).second /*inserted new?*/) { - ProcMountNSGuard g(ctx->mount_ns_instance_.get()); - bcc_elf_foreach_usdt(modpath, _each_probe, p); - } - return 0; -} - -void Context::add_probe(const char *binpath, const struct bcc_elf_usdt *probe) { - for (auto &p : probes_) { - if (p->provider_ == probe->provider && p->name_ == probe->name) { - p->add_location(probe->pc, probe->arg_fmt); - return; - } - } - - probes_.emplace_back( - new Probe(binpath, probe->provider, probe->name, probe->semaphore, pid_, - mount_ns_instance_.get())); - probes_.back()->add_location(probe->pc, probe->arg_fmt); -} - -std::string Context::resolve_bin_path(const std::string &bin_path) { - std::string result; - - if (char *which = bcc_procutils_which(bin_path.c_str())) { - result = which; - ::free(which); - } else if (char *which_so = bcc_procutils_which_so(bin_path.c_str(), 0)) { - result = which_so; - ::free(which_so); - } - - return result; -} - -Probe *Context::get(const std::string &probe_name) { - for (auto &p : probes_) { - if (p->name_ == probe_name) - return p.get(); - } - return nullptr; -} - -bool Context::enable_probe(const std::string &probe_name, - const std::string &fn_name) { - if (pid_stat_ && pid_stat_->is_stale()) - return false; - - auto p = get(probe_name); - return p && p->enable(fn_name); -} - -void Context::each(each_cb callback) { - for (const auto &probe : probes_) { - struct bcc_usdt info = {0}; - info.provider = probe->provider().c_str(); - info.bin_path = probe->bin_path().c_str(); - info.name = probe->name().c_str(); - info.semaphore = probe->semaphore(); - info.num_locations = probe->num_locations(); - info.num_arguments = probe->num_arguments(); - callback(&info); - } -} - -void Context::each_uprobe(each_uprobe_cb callback) { - for (auto &p : probes_) { - if (!p->enabled()) - continue; - - for (Location &loc : p->locations_) { - callback(p->bin_path_.c_str(), p->attached_to_->c_str(), loc.address_, - pid_.value_or(-1)); - } - } -} - -Context::Context(const std::string &bin_path) : loaded_(false) { - std::string full_path = resolve_bin_path(bin_path); - if (!full_path.empty()) { - if (bcc_elf_foreach_usdt(full_path.c_str(), _each_probe, this) == 0) { - cmd_bin_path_ = full_path; - loaded_ = true; - } - } -} - -Context::Context(int pid) : pid_(pid), pid_stat_(pid), - mount_ns_instance_(new ProcMountNS(pid)), loaded_(false) { - if (bcc_procutils_each_module(pid, _each_module, this) == 0) { - // get exe command from /proc//exe - // assume the maximum path length 4096, which should be - // sufficiently large to cover all use cases - char source[64]; - char cmd_buf[4096]; - snprintf(source, sizeof(source), "/proc/%d/exe", pid); - ssize_t cmd_len = readlink(source, cmd_buf, sizeof(cmd_buf) - 1); - if (cmd_len == -1) - return; - cmd_buf[cmd_len] = '\0'; - cmd_bin_path_.assign(cmd_buf, cmd_len + 1); - - loaded_ = true; - } -} - -Context::~Context() { - if (pid_stat_ && !pid_stat_->is_stale()) { - for (auto &p : probes_) p->disable(); - } -} -} - -extern "C" { - -void *bcc_usdt_new_frompid(int pid) { - USDT::Context *ctx = new USDT::Context(pid); - if (!ctx->loaded()) { - delete ctx; - return nullptr; - } - return static_cast(ctx); -} - -void *bcc_usdt_new_frompath(const char *path) { - USDT::Context *ctx = new USDT::Context(path); - if (!ctx->loaded()) { - delete ctx; - return nullptr; - } - return static_cast(ctx); -} - -void bcc_usdt_close(void *usdt) { - USDT::Context *ctx = static_cast(usdt); - delete ctx; -} - -int bcc_usdt_enable_probe(void *usdt, const char *probe_name, - const char *fn_name) { - USDT::Context *ctx = static_cast(usdt); - return ctx->enable_probe(probe_name, fn_name) ? 0 : -1; -} - -const char *bcc_usdt_genargs(void **usdt_array, int len) { - static std::string storage_; - std::ostringstream stream; - - if (!len) - return ""; - - stream << USDT::USDT_PROGRAM_HEADER; - // Generate genargs codes for an array of USDT Contexts. - // - // Each mnt_point + cmd_bin_path + probe_provider + probe_name - // uniquely identifies a probe. - std::unordered_set generated_probes; - for (int i = 0; i < len; i++) { - USDT::Context *ctx = static_cast(usdt_array[i]); - - for (size_t j = 0; j < ctx->num_probes(); j++) { - USDT::Probe *p = ctx->get(j); - if (p->enabled()) { - std::string key = std::to_string(ctx->inode()) + "*" - + ctx->cmd_bin_path() + "*" + p->provider() + "*" + p->name(); - if (generated_probes.find(key) != generated_probes.end()) - continue; - if (!p->usdt_getarg(stream)) - return nullptr; - generated_probes.insert(key); - } - } - } - - storage_ = stream.str(); - return storage_.c_str(); -} - -const char *bcc_usdt_get_probe_argctype( - void *ctx, const char* probe_name, const int arg_index -) { - USDT::Probe *p = static_cast(ctx)->get(probe_name); - std::string res = p ? p->get_arg_ctype(arg_index) : ""; - return res.c_str(); -} - -void bcc_usdt_foreach(void *usdt, bcc_usdt_cb callback) { - USDT::Context *ctx = static_cast(usdt); - ctx->each(callback); -} - -int bcc_usdt_get_location(void *usdt, const char *probe_name, - int index, struct bcc_usdt_location *location) { - USDT::Context *ctx = static_cast(usdt); - USDT::Probe *probe = ctx->get(probe_name); - if (!probe) - return -1; - if (index < 0 || (size_t)index >= probe->num_locations()) - return -1; - location->address = probe->address(index); - return 0; -} - -int bcc_usdt_get_argument(void *usdt, const char *probe_name, - int location_index, int argument_index, - struct bcc_usdt_argument *argument) { - USDT::Context *ctx = static_cast(usdt); - USDT::Probe *probe = ctx->get(probe_name); - if (!probe) - return -1; - if (argument_index < 0 || (size_t)argument_index >= probe->num_arguments()) - return -1; - if (location_index < 0 || (size_t)location_index >= probe->num_locations()) - return -1; - auto const &location = probe->location(location_index); - auto const &arg = location.arguments_[argument_index]; - argument->size = arg.arg_size(); - argument->valid = BCC_USDT_ARGUMENT_NONE; - if (arg.constant()) { - argument->valid |= BCC_USDT_ARGUMENT_CONSTANT; - argument->constant = *(arg.constant()); - } - if (arg.deref_offset()) { - argument->valid |= BCC_USDT_ARGUMENT_DEREF_OFFSET; - argument->deref_offset = *(arg.deref_offset()); - } - if (arg.deref_ident()) { - argument->valid |= BCC_USDT_ARGUMENT_DEREF_IDENT; - argument->deref_ident = arg.deref_ident()->c_str(); - } - if (arg.base_register_name()) { - argument->valid |= BCC_USDT_ARGUMENT_BASE_REGISTER_NAME; - argument->base_register_name = arg.base_register_name()->c_str(); - } - if (arg.index_register_name()) { - argument->valid |= BCC_USDT_ARGUMENT_INDEX_REGISTER_NAME; - argument->index_register_name = arg.index_register_name()->c_str(); - } - if (arg.scale()) { - argument->valid |= BCC_USDT_ARGUMENT_SCALE; - argument->scale = *(arg.scale()); - } - return 0; -} - -void bcc_usdt_foreach_uprobe(void *usdt, bcc_usdt_uprobe_cb callback) { - USDT::Context *ctx = static_cast(usdt); - ctx->each_uprobe(callback); -} -} diff --git a/src/cc/usdt/CMakeLists.txt b/src/cc/usdt/CMakeLists.txt new file mode 100644 index 00000000..6a2e8958 --- /dev/null +++ b/src/cc/usdt/CMakeLists.txt @@ -0,0 +1 @@ +add_library(usdt-static STATIC usdt_args.cc usdt.cc) diff --git a/src/cc/usdt/usdt.cc b/src/cc/usdt/usdt.cc new file mode 100644 index 00000000..ccb7caab --- /dev/null +++ b/src/cc/usdt/usdt.cc @@ -0,0 +1,460 @@ +/* + * Copyright (c) 2016 GitHub, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include +#include +#include + +#include +#include +#include + +#include "bcc_elf.h" +#include "bcc_proc.h" +#include "usdt.h" +#include "vendor/tinyformat.hpp" +#include "bcc_usdt.h" + +namespace USDT { + +Location::Location(uint64_t addr, const char *arg_fmt) : address_(addr) { + ArgumentParser_x64 parser(arg_fmt); + while (!parser.done()) { + Argument arg; + if (!parser.parse(&arg)) + continue; + arguments_.push_back(std::move(arg)); + } +} + +Probe::Probe(const char *bin_path, const char *provider, const char *name, + uint64_t semaphore, const optional &pid, ProcMountNS *ns) + : bin_path_(bin_path), + provider_(provider), + name_(name), + semaphore_(semaphore), + pid_(pid), + mount_ns_(ns) {} + +bool Probe::in_shared_object() { + if (!in_shared_object_) { + ProcMountNSGuard g(mount_ns_); + in_shared_object_ = bcc_elf_is_shared_obj(bin_path_.c_str()); + } + return in_shared_object_.value(); +} + +bool Probe::resolve_global_address(uint64_t *global, const uint64_t addr) { + if (in_shared_object()) { + return (pid_ && + !bcc_resolve_global_addr(*pid_, bin_path_.c_str(), addr, global)); + } + + *global = addr; + return true; +} + +bool Probe::add_to_semaphore(int16_t val) { + assert(pid_); + + if (!attached_semaphore_) { + uint64_t addr; + if (!resolve_global_address(&addr, semaphore_)) + return false; + attached_semaphore_ = addr; + } + + off_t address = static_cast(attached_semaphore_.value()); + + std::string procmem = tfm::format("/proc/%d/mem", pid_.value()); + int memfd = ::open(procmem.c_str(), O_RDWR); + if (memfd < 0) + return false; + + int16_t original; + + if (::lseek(memfd, address, SEEK_SET) < 0 || + ::read(memfd, &original, 2) != 2) { + ::close(memfd); + return false; + } + + original = original + val; + + if (::lseek(memfd, address, SEEK_SET) < 0 || + ::write(memfd, &original, 2) != 2) { + ::close(memfd); + return false; + } + + ::close(memfd); + return true; +} + +bool Probe::enable(const std::string &fn_name) { + if (attached_to_) + return false; + + if (need_enable()) { + if (!pid_) + return false; + + if (!add_to_semaphore(+1)) + return false; + } + + attached_to_ = fn_name; + return true; +} + +bool Probe::disable() { + if (!attached_to_) + return false; + + attached_to_ = nullopt; + + if (need_enable()) { + assert(pid_); + return add_to_semaphore(-1); + } + return true; +} + +std::string Probe::largest_arg_type(size_t arg_n) { + Argument *largest = nullptr; + for (Location &location : locations_) { + Argument *candidate = &location.arguments_[arg_n]; + if (!largest || + std::abs(candidate->arg_size()) > std::abs(largest->arg_size())) + largest = candidate; + } + + assert(largest); + return largest->ctype(); +} + +bool Probe::usdt_getarg(std::ostream &stream) { + const size_t arg_count = locations_[0].arguments_.size(); + + if (!attached_to_) + return false; + + if (arg_count == 0) + return true; + + for (size_t arg_n = 0; arg_n < arg_count; ++arg_n) { + std::string ctype = largest_arg_type(arg_n); + std::string cptr = tfm::format("*((%s *)dest)", ctype); + + tfm::format(stream, + "static __always_inline int _bpf_readarg_%s_%d(" + "struct pt_regs *ctx, void *dest, size_t len) {\n" + " if (len != sizeof(%s)) return -1;\n", + attached_to_.value(), arg_n + 1, ctype); + + if (locations_.size() == 1) { + Location &location = locations_.front(); + stream << " "; + if (!location.arguments_[arg_n].assign_to_local(stream, cptr, bin_path_, + pid_)) + return false; + stream << "\n return 0;\n}\n"; + } else { + stream << " switch(ctx->ip) {\n"; + for (Location &location : locations_) { + uint64_t global_address; + + if (!resolve_global_address(&global_address, location.address_)) + return false; + + tfm::format(stream, " case 0x%xULL: ", global_address); + if (!location.arguments_[arg_n].assign_to_local(stream, cptr, bin_path_, + pid_)) + return false; + + stream << " return 0;\n"; + } + stream << " }\n"; + stream << " return -1;\n}\n"; + } + } + return true; +} + +void Probe::add_location(uint64_t addr, const char *fmt) { + locations_.emplace_back(addr, fmt); +} + +void Context::_each_probe(const char *binpath, const struct bcc_elf_usdt *probe, + void *p) { + Context *ctx = static_cast(p); + ctx->add_probe(binpath, probe); +} + +int Context::_each_module(const char *modpath, uint64_t, uint64_t, bool, void *p) { + Context *ctx = static_cast(p); + // Modules may be reported multiple times if they contain more than one + // executable region. We are going to parse the ELF on disk anyway, so we + // don't need these duplicates. + if (ctx->modules_.insert(modpath).second /*inserted new?*/) { + ProcMountNSGuard g(ctx->mount_ns_instance_.get()); + bcc_elf_foreach_usdt(modpath, _each_probe, p); + } + return 0; +} + +void Context::add_probe(const char *binpath, const struct bcc_elf_usdt *probe) { + for (auto &p : probes_) { + if (p->provider_ == probe->provider && p->name_ == probe->name) { + p->add_location(probe->pc, probe->arg_fmt); + return; + } + } + + probes_.emplace_back( + new Probe(binpath, probe->provider, probe->name, probe->semaphore, pid_, + mount_ns_instance_.get())); + probes_.back()->add_location(probe->pc, probe->arg_fmt); +} + +std::string Context::resolve_bin_path(const std::string &bin_path) { + std::string result; + + if (char *which = bcc_procutils_which(bin_path.c_str())) { + result = which; + ::free(which); + } else if (char *which_so = bcc_procutils_which_so(bin_path.c_str(), 0)) { + result = which_so; + ::free(which_so); + } + + return result; +} + +Probe *Context::get(const std::string &probe_name) { + for (auto &p : probes_) { + if (p->name_ == probe_name) + return p.get(); + } + return nullptr; +} + +bool Context::enable_probe(const std::string &probe_name, + const std::string &fn_name) { + if (pid_stat_ && pid_stat_->is_stale()) + return false; + + auto p = get(probe_name); + return p && p->enable(fn_name); +} + +void Context::each(each_cb callback) { + for (const auto &probe : probes_) { + struct bcc_usdt info = {0}; + info.provider = probe->provider().c_str(); + info.bin_path = probe->bin_path().c_str(); + info.name = probe->name().c_str(); + info.semaphore = probe->semaphore(); + info.num_locations = probe->num_locations(); + info.num_arguments = probe->num_arguments(); + callback(&info); + } +} + +void Context::each_uprobe(each_uprobe_cb callback) { + for (auto &p : probes_) { + if (!p->enabled()) + continue; + + for (Location &loc : p->locations_) { + callback(p->bin_path_.c_str(), p->attached_to_->c_str(), loc.address_, + pid_.value_or(-1)); + } + } +} + +Context::Context(const std::string &bin_path) : loaded_(false) { + std::string full_path = resolve_bin_path(bin_path); + if (!full_path.empty()) { + if (bcc_elf_foreach_usdt(full_path.c_str(), _each_probe, this) == 0) { + cmd_bin_path_ = full_path; + loaded_ = true; + } + } +} + +Context::Context(int pid) : pid_(pid), pid_stat_(pid), + mount_ns_instance_(new ProcMountNS(pid)), loaded_(false) { + if (bcc_procutils_each_module(pid, _each_module, this) == 0) { + // get exe command from /proc//exe + // assume the maximum path length 4096, which should be + // sufficiently large to cover all use cases + char source[64]; + char cmd_buf[4096]; + snprintf(source, sizeof(source), "/proc/%d/exe", pid); + ssize_t cmd_len = readlink(source, cmd_buf, sizeof(cmd_buf) - 1); + if (cmd_len == -1) + return; + cmd_buf[cmd_len] = '\0'; + cmd_bin_path_.assign(cmd_buf, cmd_len + 1); + + loaded_ = true; + } +} + +Context::~Context() { + if (pid_stat_ && !pid_stat_->is_stale()) { + for (auto &p : probes_) p->disable(); + } +} +} + +extern "C" { + +void *bcc_usdt_new_frompid(int pid) { + USDT::Context *ctx = new USDT::Context(pid); + if (!ctx->loaded()) { + delete ctx; + return nullptr; + } + return static_cast(ctx); +} + +void *bcc_usdt_new_frompath(const char *path) { + USDT::Context *ctx = new USDT::Context(path); + if (!ctx->loaded()) { + delete ctx; + return nullptr; + } + return static_cast(ctx); +} + +void bcc_usdt_close(void *usdt) { + USDT::Context *ctx = static_cast(usdt); + delete ctx; +} + +int bcc_usdt_enable_probe(void *usdt, const char *probe_name, + const char *fn_name) { + USDT::Context *ctx = static_cast(usdt); + return ctx->enable_probe(probe_name, fn_name) ? 0 : -1; +} + +const char *bcc_usdt_genargs(void **usdt_array, int len) { + static std::string storage_; + std::ostringstream stream; + + if (!len) + return ""; + + stream << USDT::USDT_PROGRAM_HEADER; + // Generate genargs codes for an array of USDT Contexts. + // + // Each mnt_point + cmd_bin_path + probe_provider + probe_name + // uniquely identifies a probe. + std::unordered_set generated_probes; + for (int i = 0; i < len; i++) { + USDT::Context *ctx = static_cast(usdt_array[i]); + + for (size_t j = 0; j < ctx->num_probes(); j++) { + USDT::Probe *p = ctx->get(j); + if (p->enabled()) { + std::string key = std::to_string(ctx->inode()) + "*" + + ctx->cmd_bin_path() + "*" + p->provider() + "*" + p->name(); + if (generated_probes.find(key) != generated_probes.end()) + continue; + if (!p->usdt_getarg(stream)) + return nullptr; + generated_probes.insert(key); + } + } + } + + storage_ = stream.str(); + return storage_.c_str(); +} + +const char *bcc_usdt_get_probe_argctype( + void *ctx, const char* probe_name, const int arg_index +) { + USDT::Probe *p = static_cast(ctx)->get(probe_name); + std::string res = p ? p->get_arg_ctype(arg_index) : ""; + return res.c_str(); +} + +void bcc_usdt_foreach(void *usdt, bcc_usdt_cb callback) { + USDT::Context *ctx = static_cast(usdt); + ctx->each(callback); +} + +int bcc_usdt_get_location(void *usdt, const char *probe_name, + int index, struct bcc_usdt_location *location) { + USDT::Context *ctx = static_cast(usdt); + USDT::Probe *probe = ctx->get(probe_name); + if (!probe) + return -1; + if (index < 0 || (size_t)index >= probe->num_locations()) + return -1; + location->address = probe->address(index); + return 0; +} + +int bcc_usdt_get_argument(void *usdt, const char *probe_name, + int location_index, int argument_index, + struct bcc_usdt_argument *argument) { + USDT::Context *ctx = static_cast(usdt); + USDT::Probe *probe = ctx->get(probe_name); + if (!probe) + return -1; + if (argument_index < 0 || (size_t)argument_index >= probe->num_arguments()) + return -1; + if (location_index < 0 || (size_t)location_index >= probe->num_locations()) + return -1; + auto const &location = probe->location(location_index); + auto const &arg = location.arguments_[argument_index]; + argument->size = arg.arg_size(); + argument->valid = BCC_USDT_ARGUMENT_NONE; + if (arg.constant()) { + argument->valid |= BCC_USDT_ARGUMENT_CONSTANT; + argument->constant = *(arg.constant()); + } + if (arg.deref_offset()) { + argument->valid |= BCC_USDT_ARGUMENT_DEREF_OFFSET; + argument->deref_offset = *(arg.deref_offset()); + } + if (arg.deref_ident()) { + argument->valid |= BCC_USDT_ARGUMENT_DEREF_IDENT; + argument->deref_ident = arg.deref_ident()->c_str(); + } + if (arg.base_register_name()) { + argument->valid |= BCC_USDT_ARGUMENT_BASE_REGISTER_NAME; + argument->base_register_name = arg.base_register_name()->c_str(); + } + if (arg.index_register_name()) { + argument->valid |= BCC_USDT_ARGUMENT_INDEX_REGISTER_NAME; + argument->index_register_name = arg.index_register_name()->c_str(); + } + if (arg.scale()) { + argument->valid |= BCC_USDT_ARGUMENT_SCALE; + argument->scale = *(arg.scale()); + } + return 0; +} + +void bcc_usdt_foreach_uprobe(void *usdt, bcc_usdt_uprobe_cb callback) { + USDT::Context *ctx = static_cast(usdt); + ctx->each_uprobe(callback); +} +} diff --git a/src/cc/usdt/usdt_args.cc b/src/cc/usdt/usdt_args.cc new file mode 100644 index 00000000..42fb360f --- /dev/null +++ b/src/cc/usdt/usdt_args.cc @@ -0,0 +1,394 @@ +/* + * Copyright (c) 2016 GitHub, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include + +#include "syms.h" +#include "usdt.h" +#include "vendor/tinyformat.hpp" + +#include "bcc_elf.h" +#include "bcc_syms.h" + +namespace USDT { + +Argument::Argument() {} +Argument::~Argument() {} + +std::string Argument::ctype() const { + const int s = arg_size() * 8; + return (s < 0) ? tfm::format("int%d_t", -s) : tfm::format("uint%d_t", s); +} + +bool Argument::get_global_address(uint64_t *address, const std::string &binpath, + const optional &pid) const { + if (pid) { + static struct bcc_symbol_option default_option = { + .use_debug_file = 1, + .check_debug_file_crc = 1, + .use_symbol_type = BCC_SYM_ALL_TYPES + }; + return ProcSyms(*pid, &default_option) + .resolve_name(binpath.c_str(), deref_ident_->c_str(), address); + } + + if (!bcc_elf_is_shared_obj(binpath.c_str())) { + struct bcc_symbol sym; + if (bcc_resolve_symname(binpath.c_str(), deref_ident_->c_str(), 0x0, -1, nullptr, &sym) == 0) { + *address = sym.offset; + if (sym.module) + ::free(const_cast(sym.module)); + return true; + } + } + + return false; +} + +bool Argument::assign_to_local(std::ostream &stream, + const std::string &local_name, + const std::string &binpath, + const optional &pid) const { + if (constant_) { + tfm::format(stream, "%s = %d;", local_name, *constant_); + return true; + } + + if (!deref_offset_) { + tfm::format(stream, "%s = ctx->%s;", local_name, *base_register_name_); + // Put a compiler barrier to prevent optimization + // like llvm SimplifyCFG SinkThenElseCodeToEnd + // Volatile marking is not sufficient to prevent such optimization. + tfm::format(stream, " %s", COMPILER_BARRIER); + return true; + } + + if (deref_offset_ && !deref_ident_) { + tfm::format(stream, "{ u64 __addr = ctx->%s + %d", + *base_register_name_, *deref_offset_); + if (index_register_name_) { + int scale = scale_.value_or(1); + tfm::format(stream, " + (ctx->%s * %d);", *index_register_name_, scale); + } else { + tfm::format(stream, ";"); + } + // Theoretically, llvm SimplifyCFG SinkThenElseCodeToEnd may still + // sink bpf_probe_read call, so put a barrier here to prevent sinking + // of ctx->#fields. + tfm::format(stream, " %s ", COMPILER_BARRIER); + tfm::format(stream, + "%s __res = 0x0; " + "bpf_probe_read(&__res, sizeof(__res), (void *)__addr); " + "%s = __res; }", + ctype(), local_name); + return true; + } + + if (deref_offset_ && deref_ident_ && *base_register_name_ == "ip") { + uint64_t global_address; + if (!get_global_address(&global_address, binpath, pid)) + return false; + + tfm::format(stream, + "{ u64 __addr = 0x%xull + %d; %s __res = 0x0; " + "bpf_probe_read(&__res, sizeof(__res), (void *)__addr); " + "%s = __res; }", + global_address, *deref_offset_, ctype(), local_name); + return true; + } + + return false; +} + +ssize_t ArgumentParser::parse_number(ssize_t pos, optional *result) { + char *endp; + int number = strtol(arg_ + pos, &endp, 0); + if (endp > arg_ + pos) + *result = number; + return endp - arg_; +} + +ssize_t ArgumentParser::parse_identifier(ssize_t pos, + optional *result) { + if (isalpha(arg_[pos]) || arg_[pos] == '_') { + ssize_t start = pos++; + while (isalnum(arg_[pos]) || arg_[pos] == '_') pos++; + if (pos - start) + result->emplace(arg_ + start, pos - start); + } + return pos; +} + +ssize_t ArgumentParser::parse_register(ssize_t pos, std::string &name, + int &size) { + ssize_t start = ++pos; + if (arg_[start - 1] != '%') + return -start; + + while (isalnum(arg_[pos])) pos++; + + std::string regname(arg_ + start, pos - start); + if (!normalize_register(®name, &size)) + return -start; + + name = regname; + return pos; +} + +ssize_t ArgumentParser::parse_base_register(ssize_t pos, Argument *dest) { + int size; + std::string name; + ssize_t res = parse_register(pos, name, size); + if (res < 0) + return res; + + dest->base_register_name_ = name; + if (!dest->arg_size_) + dest->arg_size_ = size; + + return res; +} + +ssize_t ArgumentParser::parse_index_register(ssize_t pos, Argument *dest) { + int size; + std::string name; + ssize_t res = parse_register(pos, name, size); + if (res < 0) + return res; + + dest->index_register_name_ = name; + + return res; +} + +ssize_t ArgumentParser::parse_scale(ssize_t pos, Argument *dest) { + return parse_number(pos, &dest->scale_); +} + +ssize_t ArgumentParser::parse_expr(ssize_t pos, Argument *dest) { + if (arg_[pos] == '$') + return parse_number(pos + 1, &dest->constant_); + + if (arg_[pos] == '%') + return parse_base_register(pos, dest); + + if (isdigit(arg_[pos]) || arg_[pos] == '-') { + pos = parse_number(pos, &dest->deref_offset_); + if (arg_[pos] == '+') { + pos = parse_identifier(pos + 1, &dest->deref_ident_); + if (!dest->deref_ident_) + return -pos; + } + } else { + dest->deref_offset_ = 0; + pos = parse_identifier(pos, &dest->deref_ident_); + if (arg_[pos] == '+' || arg_[pos] == '-') { + pos = parse_number(pos, &dest->deref_offset_); + } + } + + if (arg_[pos] != '(') + return -pos; + + pos = parse_base_register(pos + 1, dest); + if (pos < 0) + return pos; + + if (arg_[pos] == ',') { + pos = parse_index_register(pos + 1, dest); + if (pos < 0) + return pos; + + if (arg_[pos] == ',') { + pos = parse_scale(pos + 1, dest); + if (pos < 0) + return pos; + } + } + + return (arg_[pos] == ')') ? pos + 1 : -pos; +} + +ssize_t ArgumentParser::parse_1(ssize_t pos, Argument *dest) { + if (isdigit(arg_[pos]) || arg_[pos] == '-') { + optional asize; + ssize_t m = parse_number(pos, &asize); + if (arg_[m] == '@' && asize) { + dest->arg_size_ = asize; + return parse_expr(m + 1, dest); + } + } + return parse_expr(pos, dest); +} + +void ArgumentParser::print_error(ssize_t pos) { + fprintf(stderr, "Parse error:\n %s\n", arg_); + for (ssize_t i = 0; i < pos + 4; ++i) fputc('-', stderr); + fputc('^', stderr); + fputc('\n', stderr); +} + +void ArgumentParser::skip_whitespace_from(size_t pos) { + while (isspace(arg_[pos])) pos++; + cur_pos_ = pos; +} + +void ArgumentParser::skip_until_whitespace_from(size_t pos) { + while (arg_[pos] != '\0' && !isspace(arg_[pos])) + pos++; + cur_pos_ = pos; +} + +bool ArgumentParser::parse(Argument *dest) { + if (done()) + return false; + + ssize_t res = parse_1(cur_pos_, dest); + if (res < 0) { + print_error(-res); + skip_whitespace_from(-res + 1); + return false; + } + if (!isspace(arg_[res]) && arg_[res] != '\0') { + print_error(res); + skip_until_whitespace_from(res); + return false; + } + skip_whitespace_from(res); + return true; +} + +const std::unordered_map + ArgumentParser_x64::registers_ = { + {"rax", {REG_A, 8}}, {"eax", {REG_A, 4}}, + {"ax", {REG_A, 2}}, {"al", {REG_A, 1}}, + + {"rbx", {REG_B, 8}}, {"ebx", {REG_B, 4}}, + {"bx", {REG_B, 2}}, {"bl", {REG_B, 1}}, + + {"rcx", {REG_C, 8}}, {"ecx", {REG_C, 4}}, + {"cx", {REG_C, 2}}, {"cl", {REG_C, 1}}, + + {"rdx", {REG_D, 8}}, {"edx", {REG_D, 4}}, + {"dx", {REG_D, 2}}, {"dl", {REG_D, 1}}, + + {"rsi", {REG_SI, 8}}, {"esi", {REG_SI, 4}}, + {"si", {REG_SI, 2}}, {"sil", {REG_SI, 1}}, + + {"rdi", {REG_DI, 8}}, {"edi", {REG_DI, 4}}, + {"di", {REG_DI, 2}}, {"dil", {REG_DI, 1}}, + + {"rbp", {REG_BP, 8}}, {"ebp", {REG_BP, 4}}, + {"bp", {REG_BP, 2}}, {"bpl", {REG_BP, 1}}, + + {"rsp", {REG_SP, 8}}, {"esp", {REG_SP, 4}}, + {"sp", {REG_SP, 2}}, {"spl", {REG_SP, 1}}, + + {"r8", {REG_8, 8}}, {"r8d", {REG_8, 4}}, + {"r8w", {REG_8, 2}}, {"r8b", {REG_8, 1}}, + + {"r9", {REG_9, 8}}, {"r9d", {REG_9, 4}}, + {"r9w", {REG_9, 2}}, {"r9b", {REG_9, 1}}, + + {"r10", {REG_10, 8}}, {"r10d", {REG_10, 4}}, + {"r10w", {REG_10, 2}}, {"r10b", {REG_10, 1}}, + + {"r11", {REG_11, 8}}, {"r11d", {REG_11, 4}}, + {"r11w", {REG_11, 2}}, {"r11b", {REG_11, 1}}, + + {"r12", {REG_12, 8}}, {"r12d", {REG_12, 4}}, + {"r12w", {REG_12, 2}}, {"r12b", {REG_12, 1}}, + + {"r13", {REG_13, 8}}, {"r13d", {REG_13, 4}}, + {"r13w", {REG_13, 2}}, {"r13b", {REG_13, 1}}, + + {"r14", {REG_14, 8}}, {"r14d", {REG_14, 4}}, + {"r14w", {REG_14, 2}}, {"r14b", {REG_14, 1}}, + + {"r15", {REG_15, 8}}, {"r15d", {REG_15, 4}}, + {"r15w", {REG_15, 2}}, {"r15b", {REG_15, 1}}, + + {"rip", {REG_RIP, 8}}, +}; + +void ArgumentParser_x64::reg_to_name(std::string *norm, Register reg) { + switch (reg) { + case REG_A: + *norm = "ax"; + break; + case REG_B: + *norm = "bx"; + break; + case REG_C: + *norm = "cx"; + break; + case REG_D: + *norm = "dx"; + break; + + case REG_SI: + *norm = "si"; + break; + case REG_DI: + *norm = "di"; + break; + case REG_BP: + *norm = "bp"; + break; + case REG_SP: + *norm = "sp"; + break; + + case REG_8: + *norm = "r8"; + break; + case REG_9: + *norm = "r9"; + break; + case REG_10: + *norm = "r10"; + break; + case REG_11: + *norm = "r11"; + break; + case REG_12: + *norm = "r12"; + break; + case REG_13: + *norm = "r13"; + break; + case REG_14: + *norm = "r14"; + break; + case REG_15: + *norm = "r15"; + break; + + case REG_RIP: + *norm = "ip"; + break; + } +} + +bool ArgumentParser_x64::normalize_register(std::string *reg, int *reg_size) { + auto it = registers_.find(*reg); + if (it == registers_.end()) + return false; + + *reg_size = it->second.size; + reg_to_name(reg, it->second.reg); + return true; +} +} diff --git a/src/cc/usdt_args.cc b/src/cc/usdt_args.cc deleted file mode 100644 index 42fb360f..00000000 --- a/src/cc/usdt_args.cc +++ /dev/null @@ -1,394 +0,0 @@ -/* - * Copyright (c) 2016 GitHub, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include - -#include "syms.h" -#include "usdt.h" -#include "vendor/tinyformat.hpp" - -#include "bcc_elf.h" -#include "bcc_syms.h" - -namespace USDT { - -Argument::Argument() {} -Argument::~Argument() {} - -std::string Argument::ctype() const { - const int s = arg_size() * 8; - return (s < 0) ? tfm::format("int%d_t", -s) : tfm::format("uint%d_t", s); -} - -bool Argument::get_global_address(uint64_t *address, const std::string &binpath, - const optional &pid) const { - if (pid) { - static struct bcc_symbol_option default_option = { - .use_debug_file = 1, - .check_debug_file_crc = 1, - .use_symbol_type = BCC_SYM_ALL_TYPES - }; - return ProcSyms(*pid, &default_option) - .resolve_name(binpath.c_str(), deref_ident_->c_str(), address); - } - - if (!bcc_elf_is_shared_obj(binpath.c_str())) { - struct bcc_symbol sym; - if (bcc_resolve_symname(binpath.c_str(), deref_ident_->c_str(), 0x0, -1, nullptr, &sym) == 0) { - *address = sym.offset; - if (sym.module) - ::free(const_cast(sym.module)); - return true; - } - } - - return false; -} - -bool Argument::assign_to_local(std::ostream &stream, - const std::string &local_name, - const std::string &binpath, - const optional &pid) const { - if (constant_) { - tfm::format(stream, "%s = %d;", local_name, *constant_); - return true; - } - - if (!deref_offset_) { - tfm::format(stream, "%s = ctx->%s;", local_name, *base_register_name_); - // Put a compiler barrier to prevent optimization - // like llvm SimplifyCFG SinkThenElseCodeToEnd - // Volatile marking is not sufficient to prevent such optimization. - tfm::format(stream, " %s", COMPILER_BARRIER); - return true; - } - - if (deref_offset_ && !deref_ident_) { - tfm::format(stream, "{ u64 __addr = ctx->%s + %d", - *base_register_name_, *deref_offset_); - if (index_register_name_) { - int scale = scale_.value_or(1); - tfm::format(stream, " + (ctx->%s * %d);", *index_register_name_, scale); - } else { - tfm::format(stream, ";"); - } - // Theoretically, llvm SimplifyCFG SinkThenElseCodeToEnd may still - // sink bpf_probe_read call, so put a barrier here to prevent sinking - // of ctx->#fields. - tfm::format(stream, " %s ", COMPILER_BARRIER); - tfm::format(stream, - "%s __res = 0x0; " - "bpf_probe_read(&__res, sizeof(__res), (void *)__addr); " - "%s = __res; }", - ctype(), local_name); - return true; - } - - if (deref_offset_ && deref_ident_ && *base_register_name_ == "ip") { - uint64_t global_address; - if (!get_global_address(&global_address, binpath, pid)) - return false; - - tfm::format(stream, - "{ u64 __addr = 0x%xull + %d; %s __res = 0x0; " - "bpf_probe_read(&__res, sizeof(__res), (void *)__addr); " - "%s = __res; }", - global_address, *deref_offset_, ctype(), local_name); - return true; - } - - return false; -} - -ssize_t ArgumentParser::parse_number(ssize_t pos, optional *result) { - char *endp; - int number = strtol(arg_ + pos, &endp, 0); - if (endp > arg_ + pos) - *result = number; - return endp - arg_; -} - -ssize_t ArgumentParser::parse_identifier(ssize_t pos, - optional *result) { - if (isalpha(arg_[pos]) || arg_[pos] == '_') { - ssize_t start = pos++; - while (isalnum(arg_[pos]) || arg_[pos] == '_') pos++; - if (pos - start) - result->emplace(arg_ + start, pos - start); - } - return pos; -} - -ssize_t ArgumentParser::parse_register(ssize_t pos, std::string &name, - int &size) { - ssize_t start = ++pos; - if (arg_[start - 1] != '%') - return -start; - - while (isalnum(arg_[pos])) pos++; - - std::string regname(arg_ + start, pos - start); - if (!normalize_register(®name, &size)) - return -start; - - name = regname; - return pos; -} - -ssize_t ArgumentParser::parse_base_register(ssize_t pos, Argument *dest) { - int size; - std::string name; - ssize_t res = parse_register(pos, name, size); - if (res < 0) - return res; - - dest->base_register_name_ = name; - if (!dest->arg_size_) - dest->arg_size_ = size; - - return res; -} - -ssize_t ArgumentParser::parse_index_register(ssize_t pos, Argument *dest) { - int size; - std::string name; - ssize_t res = parse_register(pos, name, size); - if (res < 0) - return res; - - dest->index_register_name_ = name; - - return res; -} - -ssize_t ArgumentParser::parse_scale(ssize_t pos, Argument *dest) { - return parse_number(pos, &dest->scale_); -} - -ssize_t ArgumentParser::parse_expr(ssize_t pos, Argument *dest) { - if (arg_[pos] == '$') - return parse_number(pos + 1, &dest->constant_); - - if (arg_[pos] == '%') - return parse_base_register(pos, dest); - - if (isdigit(arg_[pos]) || arg_[pos] == '-') { - pos = parse_number(pos, &dest->deref_offset_); - if (arg_[pos] == '+') { - pos = parse_identifier(pos + 1, &dest->deref_ident_); - if (!dest->deref_ident_) - return -pos; - } - } else { - dest->deref_offset_ = 0; - pos = parse_identifier(pos, &dest->deref_ident_); - if (arg_[pos] == '+' || arg_[pos] == '-') { - pos = parse_number(pos, &dest->deref_offset_); - } - } - - if (arg_[pos] != '(') - return -pos; - - pos = parse_base_register(pos + 1, dest); - if (pos < 0) - return pos; - - if (arg_[pos] == ',') { - pos = parse_index_register(pos + 1, dest); - if (pos < 0) - return pos; - - if (arg_[pos] == ',') { - pos = parse_scale(pos + 1, dest); - if (pos < 0) - return pos; - } - } - - return (arg_[pos] == ')') ? pos + 1 : -pos; -} - -ssize_t ArgumentParser::parse_1(ssize_t pos, Argument *dest) { - if (isdigit(arg_[pos]) || arg_[pos] == '-') { - optional asize; - ssize_t m = parse_number(pos, &asize); - if (arg_[m] == '@' && asize) { - dest->arg_size_ = asize; - return parse_expr(m + 1, dest); - } - } - return parse_expr(pos, dest); -} - -void ArgumentParser::print_error(ssize_t pos) { - fprintf(stderr, "Parse error:\n %s\n", arg_); - for (ssize_t i = 0; i < pos + 4; ++i) fputc('-', stderr); - fputc('^', stderr); - fputc('\n', stderr); -} - -void ArgumentParser::skip_whitespace_from(size_t pos) { - while (isspace(arg_[pos])) pos++; - cur_pos_ = pos; -} - -void ArgumentParser::skip_until_whitespace_from(size_t pos) { - while (arg_[pos] != '\0' && !isspace(arg_[pos])) - pos++; - cur_pos_ = pos; -} - -bool ArgumentParser::parse(Argument *dest) { - if (done()) - return false; - - ssize_t res = parse_1(cur_pos_, dest); - if (res < 0) { - print_error(-res); - skip_whitespace_from(-res + 1); - return false; - } - if (!isspace(arg_[res]) && arg_[res] != '\0') { - print_error(res); - skip_until_whitespace_from(res); - return false; - } - skip_whitespace_from(res); - return true; -} - -const std::unordered_map - ArgumentParser_x64::registers_ = { - {"rax", {REG_A, 8}}, {"eax", {REG_A, 4}}, - {"ax", {REG_A, 2}}, {"al", {REG_A, 1}}, - - {"rbx", {REG_B, 8}}, {"ebx", {REG_B, 4}}, - {"bx", {REG_B, 2}}, {"bl", {REG_B, 1}}, - - {"rcx", {REG_C, 8}}, {"ecx", {REG_C, 4}}, - {"cx", {REG_C, 2}}, {"cl", {REG_C, 1}}, - - {"rdx", {REG_D, 8}}, {"edx", {REG_D, 4}}, - {"dx", {REG_D, 2}}, {"dl", {REG_D, 1}}, - - {"rsi", {REG_SI, 8}}, {"esi", {REG_SI, 4}}, - {"si", {REG_SI, 2}}, {"sil", {REG_SI, 1}}, - - {"rdi", {REG_DI, 8}}, {"edi", {REG_DI, 4}}, - {"di", {REG_DI, 2}}, {"dil", {REG_DI, 1}}, - - {"rbp", {REG_BP, 8}}, {"ebp", {REG_BP, 4}}, - {"bp", {REG_BP, 2}}, {"bpl", {REG_BP, 1}}, - - {"rsp", {REG_SP, 8}}, {"esp", {REG_SP, 4}}, - {"sp", {REG_SP, 2}}, {"spl", {REG_SP, 1}}, - - {"r8", {REG_8, 8}}, {"r8d", {REG_8, 4}}, - {"r8w", {REG_8, 2}}, {"r8b", {REG_8, 1}}, - - {"r9", {REG_9, 8}}, {"r9d", {REG_9, 4}}, - {"r9w", {REG_9, 2}}, {"r9b", {REG_9, 1}}, - - {"r10", {REG_10, 8}}, {"r10d", {REG_10, 4}}, - {"r10w", {REG_10, 2}}, {"r10b", {REG_10, 1}}, - - {"r11", {REG_11, 8}}, {"r11d", {REG_11, 4}}, - {"r11w", {REG_11, 2}}, {"r11b", {REG_11, 1}}, - - {"r12", {REG_12, 8}}, {"r12d", {REG_12, 4}}, - {"r12w", {REG_12, 2}}, {"r12b", {REG_12, 1}}, - - {"r13", {REG_13, 8}}, {"r13d", {REG_13, 4}}, - {"r13w", {REG_13, 2}}, {"r13b", {REG_13, 1}}, - - {"r14", {REG_14, 8}}, {"r14d", {REG_14, 4}}, - {"r14w", {REG_14, 2}}, {"r14b", {REG_14, 1}}, - - {"r15", {REG_15, 8}}, {"r15d", {REG_15, 4}}, - {"r15w", {REG_15, 2}}, {"r15b", {REG_15, 1}}, - - {"rip", {REG_RIP, 8}}, -}; - -void ArgumentParser_x64::reg_to_name(std::string *norm, Register reg) { - switch (reg) { - case REG_A: - *norm = "ax"; - break; - case REG_B: - *norm = "bx"; - break; - case REG_C: - *norm = "cx"; - break; - case REG_D: - *norm = "dx"; - break; - - case REG_SI: - *norm = "si"; - break; - case REG_DI: - *norm = "di"; - break; - case REG_BP: - *norm = "bp"; - break; - case REG_SP: - *norm = "sp"; - break; - - case REG_8: - *norm = "r8"; - break; - case REG_9: - *norm = "r9"; - break; - case REG_10: - *norm = "r10"; - break; - case REG_11: - *norm = "r11"; - break; - case REG_12: - *norm = "r12"; - break; - case REG_13: - *norm = "r13"; - break; - case REG_14: - *norm = "r14"; - break; - case REG_15: - *norm = "r15"; - break; - - case REG_RIP: - *norm = "ip"; - break; - } -} - -bool ArgumentParser_x64::normalize_register(std::string *reg, int *reg_size) { - auto it = registers_.find(*reg); - if (it == registers_.end()) - return false; - - *reg_size = it->second.size; - reg_to_name(reg, it->second.reg); - return true; -} -}