1688a027d8
- additional miscellaneous tweaks to workflows and docker scripts, e.g. install perfetto python bindings - improves the stability of MPI finalization - reduces some debug messages within timemory when `OMNITRACE_DEBUG=ON` - fixes issue found in RHEL where libunwind is using mutex and omnitrace was not treating this as an internal mutex call - this may have been affecting the causal profiling slightly (tests seem a bit more stable now) - fix data race in timemory * Add RedHat CI and release packaging - additional miscellaneous tweaks to workflows and docker scripts, e.g. install perfetto python bindings * Fix URL for ROCm packages in redhat workflow * Fix dnf --enable-repo for ROCm perl packages * Dockerfile.rhel and redhat.yml updates - Fix dnf repo for ROCm PERL packages - Disable python in CI (interpreter segfaults) - Exclude parallel-overhead-locks tests due to inclusion of internal locks - This needs to be remedied in the future * Exclude _dl_relocate_static_pie from instrumentation * Testing updates - OMNITRACE_SAMPLING_KEEP_INTERNAL=OFF for parallel-overhead-locks * Fix redhat workflow * redhat.yml update - remove if condition on config/build/test step * Update timemory submodule - tweaks to verbosity messages * Set thread state before unw_step - on Redhat, unw_step calls mutex * Update timemory submodule - verbosity changes - gotcha uses spin_lock/spin_mutex * Remove using gsplit-dwarf unless OMNITRACE_BUILD_NUMBER > 2 * Re-enable parallel-overhead-locks tests in redhat workflow * Always disable timemory manager metadata auto output * testing updates - tweak parallel-overhead-locks-timemory to higher instruction count min - OMNITRACE_SAMPLING_KEEP_INTERNAL=OFF for parallel-overhead-locks-perfetto * Update timemory submodule - quiet realpath queries * omnitrace exe updates - detect text files - improved bin/lib locating * cmake format * test-install.sh and redhat workflow updates - handle testing when ls is script - re-enable python testing on redhat workflow - invoke test-install.sh in redhat workflow * Misc guards for finalization * omnitrace-exe, testing updates - test-install.sh: LS_EXEC -> LS_NAME - handle /usr/bin/ls being script in source/bin/tests - improve locating the binary * Fix mpi_gotcha compile error * omnitrace-exe updates - improve file locating * formatting * Misc fixes - remove -static-libstdc++ for RHEL packaging (rocky-linux doesn't distribute static lib) * omnitrace-exe paths * Replace realpath with absolute - using absolute path to symlink fixes issues with locating libdyninstAPI_RT at runtime * omnitrace exe updates - judicious use of realpath * Update timemory submodule - fix update main hash ids/aliases data race in merge * bin tests update - change working directory of omnitrace-exe-simulate-lib-basename * omnitrace exe updates - Update resolved exe/lib messaging * bin tests update - change working directory of omnitrace-exe-simulate-lib-basename
2812 wiersze
111 KiB
C++
2812 wiersze
111 KiB
C++
// MIT License
|
|
//
|
|
// Copyright (c) 2022 Advanced Micro Devices, Inc. All Rights Reserved.
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
// of this software and associated documentation files (the "Software"), to deal
|
|
// in the Software without restriction, including without limitation the rights
|
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
// copies of the Software, and to permit persons to whom the Software is
|
|
// furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in all
|
|
// copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
// SOFTWARE.
|
|
|
|
#include "omnitrace.hpp"
|
|
#include "common/defines.h"
|
|
#include "fwd.hpp"
|
|
#include "internal_libs.hpp"
|
|
#include "log.hpp"
|
|
|
|
#include <timemory/backends/process.hpp>
|
|
#include <timemory/config.hpp>
|
|
#include <timemory/environment/types.hpp>
|
|
#include <timemory/hash.hpp>
|
|
#include <timemory/log/macros.hpp>
|
|
#include <timemory/manager.hpp>
|
|
#include <timemory/settings.hpp>
|
|
#include <timemory/signals/signal_mask.hpp>
|
|
#include <timemory/utility/console.hpp>
|
|
#include <timemory/utility/delimit.hpp>
|
|
#include <timemory/utility/demangle.hpp>
|
|
#include <timemory/utility/filepath.hpp>
|
|
#include <timemory/utility/signals.hpp>
|
|
|
|
#include <algorithm>
|
|
#include <chrono>
|
|
#include <csignal>
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <iomanip>
|
|
#include <iterator>
|
|
#include <map>
|
|
#include <regex>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <thread>
|
|
#include <tuple>
|
|
#include <unistd.h>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#if !defined(OMNITRACE_USE_MPI)
|
|
# define OMNITRACE_USE_MPI 0
|
|
#endif
|
|
|
|
#if !defined(OMNITRACE_USE_MPI_HEADERS)
|
|
# define OMNITRACE_USE_MPI_HEADERS 0
|
|
#endif
|
|
|
|
namespace
|
|
{
|
|
auto
|
|
get_default_min_instructions()
|
|
{
|
|
// default to 1024
|
|
return tim::get_env<size_t>("OMNITRACE_DEFAULT_MIN_INSTRUCTIONS", (1 << 10), false);
|
|
}
|
|
auto
|
|
get_default_min_address_range()
|
|
{
|
|
// default to 4096
|
|
return 4 * get_default_min_instructions();
|
|
}
|
|
} // namespace
|
|
|
|
bool use_return_info = false;
|
|
bool use_args_info = false;
|
|
bool use_file_info = false;
|
|
bool use_line_info = false;
|
|
bool allow_overlapping = false;
|
|
bool loop_level_instr = false;
|
|
bool instr_dynamic_callsites = false;
|
|
bool instr_traps = false;
|
|
bool instr_loop_traps = false;
|
|
bool parse_all_modules = false;
|
|
size_t min_address_range = get_default_min_address_range(); // 4096
|
|
size_t min_loop_address_range = get_default_min_address_range(); // 4096
|
|
size_t min_instructions = get_default_min_instructions(); // 1024
|
|
size_t min_loop_instructions = get_default_min_instructions(); // 1024
|
|
bool werror = false;
|
|
bool debug_print = false;
|
|
bool instr_print = false;
|
|
bool simulate = false;
|
|
bool include_uninstr = false;
|
|
bool include_internal_linked_libs = false;
|
|
int verbose_level = tim::get_env<int>("OMNITRACE_VERBOSE_INSTRUMENT", 0);
|
|
int num_log_entries = tim::get_env<int>(
|
|
"OMNITRACE_LOG_COUNT", tim::get_env<bool>("OMNITRACE_CI", false) ? 20 : -1);
|
|
string_t main_fname = "main";
|
|
string_t argv0 = {};
|
|
string_t cmdv0 = {};
|
|
string_t default_components = "wall_clock";
|
|
string_t prefer_library = {};
|
|
//
|
|
// global variables
|
|
//
|
|
patch_pointer_t bpatch = {};
|
|
call_expr_t* terminate_expr = nullptr;
|
|
snippet_vec_t init_names = {};
|
|
snippet_vec_t fini_names = {};
|
|
fmodset_t available_module_functions = {};
|
|
fmodset_t instrumented_module_functions = {};
|
|
fmodset_t coverage_module_functions = {};
|
|
fmodset_t overlapping_module_functions = {};
|
|
fmodset_t excluded_module_functions = {};
|
|
fixed_modset_t fixed_module_functions = {};
|
|
regexvec_t func_include = {};
|
|
regexvec_t func_exclude = {};
|
|
regexvec_t file_include = {};
|
|
regexvec_t file_exclude = {};
|
|
regexvec_t file_restrict = {};
|
|
regexvec_t func_restrict = {};
|
|
regexvec_t caller_include = {};
|
|
regexvec_t func_internal_include = {};
|
|
regexvec_t file_internal_include = {};
|
|
regexvec_t instruction_exclude = {};
|
|
CodeCoverageMode coverage_mode = CODECOV_NONE;
|
|
|
|
symtab_data_s symtab_data = {};
|
|
std::set<symbol_linkage_t> enabled_linkage = { SL_GLOBAL, SL_LOCAL, SL_UNIQUE };
|
|
std::set<symbol_visibility_t> enabled_visibility = { SV_DEFAULT, SV_HIDDEN, SV_INTERNAL,
|
|
SV_PROTECTED };
|
|
|
|
std::unique_ptr<std::ofstream> log_ofs = {};
|
|
|
|
namespace
|
|
{
|
|
namespace process = tim::process; // NOLINT
|
|
namespace signals = tim::signals;
|
|
namespace filepath = tim::filepath;
|
|
|
|
using signal_settings = tim::signals::signal_settings;
|
|
using sys_signal = tim::signals::sys_signal;
|
|
|
|
bool binary_rewrite = false;
|
|
bool is_attached = false;
|
|
bool use_mpi = false;
|
|
bool is_static_exe = false;
|
|
bool force_config = false;
|
|
size_t batch_size = 50;
|
|
strset_t extra_libs = {};
|
|
std::vector<std::pair<uint64_t, string_t>> hash_ids = {};
|
|
std::map<string_t, bool> use_stubs = {};
|
|
std::map<string_t, procedure_t*> beg_stubs = {};
|
|
std::map<string_t, procedure_t*> end_stubs = {};
|
|
strvec_t init_stub_names = {};
|
|
strvec_t fini_stub_names = {};
|
|
strset_t used_stub_names = {};
|
|
strvec_t env_config_variables = {};
|
|
std::vector<call_expr_pointer_t> env_variables = {};
|
|
std::map<string_t, call_expr_pointer_t> beg_expr = {};
|
|
std::map<string_t, call_expr_pointer_t> end_expr = {};
|
|
const auto npos_v = string_t::npos;
|
|
string_t instr_mode = "trace";
|
|
string_t print_coverage = {};
|
|
string_t print_instrumented = {};
|
|
string_t print_excluded = {};
|
|
string_t print_available = {};
|
|
string_t print_overlapping = {};
|
|
strset_t print_formats = { "txt", "json" };
|
|
std::string modfunc_dump_dir = {};
|
|
auto regex_opts = std::regex_constants::egrep | std::regex_constants::optimize;
|
|
|
|
strvec_t lib_search_paths =
|
|
tim::delimit(JOIN(':', tim::get_env<std::string>("DYNINSTAPI_RT_LIB"),
|
|
tim::get_env<std::string>("DYNINST_REWRITER_PATHS"),
|
|
tim::get_env<std::string>("LD_LIBRARY_PATH")),
|
|
":");
|
|
strvec_t bin_search_paths = tim::delimit(tim::get_env<std::string>("PATH"), ":");
|
|
|
|
#if defined(DYNINST_API_RT)
|
|
auto _dyn_api_rt_paths = tim::delimit(DYNINST_API_RT, ":");
|
|
#else
|
|
auto _dyn_api_rt_paths = std::vector<std::string>{};
|
|
#endif
|
|
|
|
std::string
|
|
get_absolute_filepath(std::string _name, const strvec_t& _paths);
|
|
|
|
std::string
|
|
get_absolute_filepath(std::string _name);
|
|
|
|
std::string
|
|
get_absolute_exe_filepath(std::string exe_name);
|
|
|
|
std::string
|
|
get_absolute_lib_filepath(std::string lib_name);
|
|
|
|
bool
|
|
exists(const std::string& name);
|
|
|
|
bool
|
|
is_file(std::string _name);
|
|
|
|
bool
|
|
is_directory(std::string _name);
|
|
|
|
std::string
|
|
get_realpath(const std::string&);
|
|
|
|
std::string
|
|
get_cwd();
|
|
|
|
void
|
|
find_dyn_api_rt();
|
|
|
|
void
|
|
activate_signal_handlers(const std::vector<sys_signal>& _signals)
|
|
{
|
|
for(const auto& itr : _signals)
|
|
signal_settings::enable(itr);
|
|
|
|
static bool _protect = false;
|
|
auto _exit_action = [](int nsig) {
|
|
if(_protect) return;
|
|
_protect = true;
|
|
TIMEMORY_PRINTF_FATAL(
|
|
stderr, "omnitrace exited with signal %i :: %s\n", nsig,
|
|
signal_settings::str(static_cast<sys_signal>(nsig)).c_str());
|
|
|
|
// print any forced entries
|
|
print_log_entries(
|
|
std::cerr, -1, [](const auto& _v) { return _v.forced(); },
|
|
[]() {
|
|
tim::log::stream(std::cerr, tim::log::color::info())
|
|
<< "\n[omnitrace][exe] Potentially important log entries:\n\n";
|
|
});
|
|
|
|
// print the last log entries
|
|
print_log_entries(std::cerr, num_log_entries);
|
|
|
|
TIMEMORY_PRINTF_FATAL(stderr, "\n");
|
|
TIMEMORY_PRINTF_FATAL(
|
|
stderr,
|
|
"These were the last %i log entries from omnitrace. You can control the "
|
|
"number of log entries via the '--log <N>' option or OMNITRACE_LOG_COUNT "
|
|
"env variable.\n",
|
|
num_log_entries);
|
|
|
|
if(log_ofs) log_ofs->close();
|
|
log_ofs.reset();
|
|
|
|
_protect = false;
|
|
};
|
|
|
|
signal_settings::set_exit_action(_exit_action);
|
|
signal_settings::check_environment();
|
|
signals::enable_signal_detection(signal_settings::get_enabled());
|
|
}
|
|
|
|
// default signals to catch
|
|
auto _activate =
|
|
(activate_signal_handlers({ sys_signal::Interrupt, sys_signal::FPE, sys_signal::Stop,
|
|
sys_signal::Quit, sys_signal::Illegal, sys_signal::Abort,
|
|
sys_signal::Bus, sys_signal::SegFault,
|
|
sys_signal::FileSize, sys_signal::CPUtime }),
|
|
true);
|
|
|
|
auto
|
|
find(const std::string& itr, const strvec_t& _data)
|
|
{
|
|
return std::any_of(_data.begin(), _data.end(),
|
|
[itr](const auto& _v) { return (itr == _v); });
|
|
}
|
|
} // namespace
|
|
|
|
//======================================================================================//
|
|
//
|
|
// entry point
|
|
//
|
|
//======================================================================================//
|
|
//
|
|
int
|
|
main(int argc, char** argv)
|
|
{
|
|
argv0 = argv[0];
|
|
|
|
auto _omni_root = tim::get_env<std::string>(
|
|
"omnitrace_ROOT", tim::get_env<std::string>("OMNITRACE_ROOT", ""));
|
|
if(!_omni_root.empty() && exists(_omni_root))
|
|
{
|
|
bin_search_paths.emplace_back(JOIN('/', _omni_root, "bin"));
|
|
bin_search_paths.emplace_back(JOIN('/', _omni_root, "lib", "omnitrace"));
|
|
bin_search_paths.emplace_back(JOIN('/', _omni_root, "lib", "omnitrace", "bin"));
|
|
lib_search_paths.emplace_back(JOIN('/', _omni_root, "lib"));
|
|
lib_search_paths.emplace_back(JOIN('/', _omni_root, "lib", "omnitrace"));
|
|
lib_search_paths.emplace_back(JOIN('/', _omni_root, "lib", "omnitrace", "lib"));
|
|
lib_search_paths.emplace_back(JOIN('/', _omni_root, "lib", "omnitrace", "lib64"));
|
|
OMNITRACE_ADD_LOG_ENTRY(argv[0], "::", "omnitrace root path: ", _omni_root);
|
|
}
|
|
|
|
auto _omni_exe_path = get_realpath(get_absolute_exe_filepath(argv[0]));
|
|
if(!exists(_omni_exe_path))
|
|
_omni_exe_path =
|
|
get_realpath(get_absolute_exe_filepath(omnitrace_get_exe_realpath()));
|
|
bin_search_paths.emplace_back(filepath::dirname(_omni_exe_path));
|
|
|
|
auto _omni_lib_path =
|
|
JOIN('/', filepath::dirname(filepath::dirname(_omni_exe_path)), "lib");
|
|
bin_search_paths.emplace_back(JOIN('/', _omni_lib_path, "omnitrace"));
|
|
bin_search_paths.emplace_back(JOIN('/', _omni_lib_path, "omnitrace", "bin"));
|
|
lib_search_paths.emplace_back(_omni_lib_path);
|
|
lib_search_paths.emplace_back(JOIN('/', _omni_lib_path, "omnitrace"));
|
|
lib_search_paths.emplace_back(JOIN('/', _omni_lib_path, "omnitrace", "lib"));
|
|
lib_search_paths.emplace_back(JOIN('/', _omni_lib_path, "omnitrace", "lib64"));
|
|
|
|
OMNITRACE_ADD_LOG_ENTRY(argv[0], "::", "omnitrace bin path: ", _omni_exe_path);
|
|
OMNITRACE_ADD_LOG_ENTRY(argv[0], "::", "omnitrace lib path: ", _omni_lib_path);
|
|
|
|
for(const auto& itr : omnitrace_get_link_map(nullptr))
|
|
{
|
|
if(itr.find("omnitrace") != std::string::npos ||
|
|
std::regex_search(
|
|
itr, std::regex{ "lib(dyninstAPI|stackwalk|pcontrol|patchAPI|parseAPI|"
|
|
"instructionAPI|symtabAPI|dynDwarf|common|dynElf|tbb|"
|
|
"tbbmalloc|tbbmalloc_proxy|gotcha|libunwind|roctracer|"
|
|
"hsa-runtime|amdhip|rocm_smi)\\.(so|a)" }))
|
|
{
|
|
if(!find(filepath::dirname(itr), lib_search_paths))
|
|
lib_search_paths.emplace_back(filepath::dirname(itr));
|
|
}
|
|
}
|
|
|
|
// DO NOT SORT! Just remove adjacent duplicates
|
|
bin_search_paths.erase(std::unique(bin_search_paths.begin(), bin_search_paths.end()),
|
|
bin_search_paths.end());
|
|
lib_search_paths.erase(std::unique(lib_search_paths.begin(), lib_search_paths.end()),
|
|
lib_search_paths.end());
|
|
|
|
for(const auto& itr : bin_search_paths)
|
|
{
|
|
OMNITRACE_ADD_LOG_ENTRY("bin search path:", itr);
|
|
}
|
|
|
|
for(const auto& itr : lib_search_paths)
|
|
{
|
|
OMNITRACE_ADD_LOG_ENTRY("lib search path:", itr);
|
|
}
|
|
|
|
address_space_t* addr_space = nullptr;
|
|
string_t mutname = {};
|
|
string_t outfile = {};
|
|
string_t logfile = {};
|
|
std::vector<string_t> inputlib = { "libomnitrace-dl" };
|
|
std::vector<string_t> libname = {};
|
|
std::vector<string_t> sharedlibname = {};
|
|
std::vector<string_t> staticlibname = {};
|
|
process::id_t _pid = -1;
|
|
|
|
fixed_module_functions = {
|
|
{ &available_module_functions, false },
|
|
{ &instrumented_module_functions, false },
|
|
{ &coverage_module_functions, false },
|
|
{ &excluded_module_functions, false },
|
|
{ &overlapping_module_functions, false },
|
|
};
|
|
|
|
std::set<std::string> dyninst_defs = { "TypeChecking", "SaveFPR", "DelayedParsing",
|
|
"DebugParsing", "MergeTramp" };
|
|
|
|
int _argc = argc;
|
|
int _cmdc = 0;
|
|
|
|
char** _argv = new char*[_argc];
|
|
char** _cmdv = nullptr;
|
|
|
|
for(int i = 0; i < argc; ++i)
|
|
_argv[i] = nullptr;
|
|
|
|
auto copy_str = [](char*& _dst, const char* _src) { _dst = strdup(_src); };
|
|
|
|
copy_str(_argv[0], argv[0]);
|
|
|
|
for(int i = 1; i < argc; ++i)
|
|
{
|
|
string_t _arg = argv[i];
|
|
if(_arg.length() == 2 && _arg == "--")
|
|
{
|
|
_argc = i;
|
|
_cmdc = argc - i - 1;
|
|
_cmdv = new char*[_cmdc + 1];
|
|
_cmdv[_cmdc] = nullptr;
|
|
int k = 0;
|
|
for(int j = i + 1; j < argc; ++j, ++k)
|
|
{
|
|
auto _v =
|
|
std::regex_replace(argv[j], std::regex{ "(.*)([ \t\n\r]+)$" }, "$1");
|
|
copy_str(_cmdv[k], _v.c_str());
|
|
}
|
|
if(_cmdc > 0) mutname = _cmdv[0];
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
copy_str(_argv[i], argv[i]);
|
|
}
|
|
}
|
|
|
|
auto cmd_string = [](int _ac, char** _av) -> std::string {
|
|
if(_ac == 0) return std::string{};
|
|
stringstream_t ss;
|
|
for(int i = 0; i < _ac; ++i)
|
|
ss << " " << _av[i];
|
|
return ss.str().substr(1);
|
|
};
|
|
|
|
if(_cmdc > 0 && !mutname.empty())
|
|
{
|
|
auto resolved_mutname = get_realpath(get_absolute_filepath(mutname));
|
|
if(resolved_mutname != mutname)
|
|
{
|
|
mutname = resolved_mutname;
|
|
free(_cmdv[0]);
|
|
copy_str(_cmdv[0], resolved_mutname.c_str());
|
|
}
|
|
}
|
|
|
|
if(verbose_level > 1)
|
|
{
|
|
std::cout << "[omnitrace][exe][original]: " << cmd_string(argc, argv)
|
|
<< std::endl;
|
|
std::cout << "[omnitrace][exe][cfg-args]: " << cmd_string(_argc, _argv)
|
|
<< std::endl;
|
|
}
|
|
|
|
if(_cmdc > 0) cmdv0 = _cmdv[0];
|
|
|
|
// now can loop through the options. If the first character is '-', then we know
|
|
// we have an option. Check to see if it is one of our options and process it. If
|
|
// it is unrecognized, then set the errflag to report an error. When we come to a
|
|
// non '-' charcter, then we must be at the application name.
|
|
using parser_t = tim::argparse::argument_parser;
|
|
parser_t parser("omnitrace");
|
|
string_t extra_help = "-- <CMD> <ARGS>";
|
|
|
|
parser.enable_help();
|
|
parser.enable_version("omnitrace", "v" OMNITRACE_VERSION_STRING,
|
|
OMNITRACE_GIT_DESCRIBE, OMNITRACE_GIT_REVISION);
|
|
|
|
parser.add_argument({ "" }, "");
|
|
parser.add_argument({ "[DEBUG OPTIONS]" }, "");
|
|
parser.add_argument({ "" }, "");
|
|
parser.add_argument({ "-v", "--verbose" }, "Verbose output")
|
|
.max_count(1)
|
|
.action([](parser_t& p) {
|
|
if(p.get_count("v") == 0)
|
|
verbose_level = 1;
|
|
else
|
|
verbose_level = p.get<int>("v");
|
|
});
|
|
parser.add_argument({ "-e", "--error" }, "All warnings produce runtime errors")
|
|
.dtype("boolean")
|
|
.max_count(1)
|
|
.action([](parser_t& p) { werror = p.get<bool>("error"); });
|
|
parser.add_argument({ "--debug" }, "Debug output")
|
|
.max_count(1)
|
|
.action([](parser_t& p) {
|
|
debug_print = p.get<bool>("debug");
|
|
if(debug_print && !p.exists("verbose")) verbose_level = 256;
|
|
});
|
|
parser
|
|
.add_argument({ "--log" }, "Number of log entries to display after an error. Any "
|
|
"value < 0 will emit the entire log")
|
|
.count(1)
|
|
.action([](parser_t& p) { num_log_entries = p.get<int>("log"); });
|
|
parser
|
|
.add_argument({ "--log-file" },
|
|
"Write the log out the specified file during the run")
|
|
.count(1)
|
|
.action([&logfile](parser_t& p) {
|
|
auto _oname = p.get<std::string>("log-file");
|
|
auto _cfg = tim::settings::compose_filename_config{};
|
|
_cfg.subdirectory = "instrumentation";
|
|
logfile = tim::settings::compose_output_filename(_oname, "log", _cfg);
|
|
});
|
|
parser
|
|
.add_argument({ "--simulate" },
|
|
"Exit after outputting diagnostic "
|
|
"{available,instrumented,excluded,overlapping} module "
|
|
"function lists, e.g. available.txt")
|
|
.max_count(1)
|
|
.dtype("boolean")
|
|
.action([](parser_t& p) { simulate = p.get<bool>("simulate"); });
|
|
parser
|
|
.add_argument({ "--print-format" },
|
|
"Output format for diagnostic "
|
|
"{available,instrumented,excluded,overlapping} module "
|
|
"function lists, e.g. {print-dir}/available.txt")
|
|
.min_count(1)
|
|
.max_count(3)
|
|
.dtype("string")
|
|
.choices({ "xml", "json", "txt" })
|
|
.action([](parser_t& p) { print_formats = p.get<strset_t>("print-format"); });
|
|
parser
|
|
.add_argument({ "--print-dir" },
|
|
"Output directory for diagnostic "
|
|
"{available,instrumented,excluded,overlapping} module "
|
|
"function lists, e.g. {print-dir}/available.txt")
|
|
.count(1)
|
|
.dtype("string")
|
|
.action([](parser_t& p) {
|
|
tim::settings::output_path() = p.get<std::string>("print-dir");
|
|
});
|
|
parser
|
|
.add_argument(
|
|
{ "--print-available" },
|
|
"Print the available entities for instrumentation (functions, modules, or "
|
|
"module-function pair) to stdout after applying regular expressions")
|
|
.count(1)
|
|
.choices({ "functions", "modules", "functions+", "pair", "pair+" })
|
|
.action(
|
|
[](parser_t& p) { print_available = p.get<std::string>("print-available"); });
|
|
parser
|
|
.add_argument(
|
|
{ "--print-instrumented" },
|
|
"Print the instrumented entities (functions, modules, or module-function "
|
|
"pair) to stdout after applying regular expressions")
|
|
.count(1)
|
|
.choices({ "functions", "modules", "functions+", "pair", "pair+" })
|
|
.action([](parser_t& p) {
|
|
print_instrumented = p.get<std::string>("print-instrumented");
|
|
});
|
|
parser
|
|
.add_argument({ "--print-coverage" },
|
|
"Print the instrumented coverage entities (functions, modules, or "
|
|
"module-function "
|
|
"pair) to stdout after applying regular expressions")
|
|
.count(1)
|
|
.choices({ "functions", "modules", "functions+", "pair", "pair+" })
|
|
.action(
|
|
[](parser_t& p) { print_coverage = p.get<std::string>("print-coverage"); });
|
|
parser
|
|
.add_argument({ "--print-excluded" },
|
|
"Print the entities for instrumentation (functions, modules, or "
|
|
"module-function "
|
|
"pair) which are excluded from the instrumentation to stdout after "
|
|
"applying regular expressions")
|
|
.count(1)
|
|
.choices({ "functions", "modules", "functions+", "pair", "pair+" })
|
|
.action(
|
|
[](parser_t& p) { print_excluded = p.get<std::string>("print-excluded"); });
|
|
parser
|
|
.add_argument(
|
|
{ "--print-overlapping" },
|
|
"Print the entities for instrumentation (functions, modules, or "
|
|
"module-function pair) which overlap other function calls or have multiple "
|
|
"entry points to stdout after applying regular expressions")
|
|
.count(1)
|
|
.choices({ "functions", "modules", "functions+", "pair", "pair+" })
|
|
.action([](parser_t& p) {
|
|
print_overlapping = p.get<std::string>("print-overlapping");
|
|
});
|
|
parser
|
|
.add_argument(
|
|
{ "--print-instructions" },
|
|
"Print the instructions for each basic-block in the JSON/XML outputs")
|
|
.max_count(1)
|
|
.action([](parser_t& p) { instr_print = p.get<bool>("print-instructions"); });
|
|
|
|
parser.add_argument({ "" }, "");
|
|
parser.add_argument({ "[MODE OPTIONS]" }, "");
|
|
parser.add_argument({ "" }, "");
|
|
parser
|
|
.add_argument({ "-o", "--output" },
|
|
"Enable generation of a new executable (binary-rewrite). If a "
|
|
"filename is not provided, omnitrace will use the basename and "
|
|
"output to the cwd, unless the target binary is in the cwd. In the "
|
|
"latter case, omnitrace will either use ${PWD}/<basename>.inst "
|
|
"(non-libraries) or ${PWD}/instrumented/<basename> (libraries)")
|
|
.min_count(0)
|
|
.max_count(1)
|
|
.dtype("string")
|
|
.action([&outfile](parser_t& p) {
|
|
binary_rewrite = true;
|
|
outfile = p.get<string_t>("output");
|
|
});
|
|
parser.add_argument({ "-p", "--pid" }, "Connect to running process")
|
|
.dtype("int")
|
|
.count(1)
|
|
.action([&_pid](parser_t& p) { _pid = p.get<int>("pid"); });
|
|
parser
|
|
.add_argument({ "-M", "--mode" },
|
|
"Instrumentation mode. 'trace' mode instruments the selected "
|
|
"functions, 'sampling' mode only instruments the main function to "
|
|
"start and stop the sampler.")
|
|
.choices({ "trace", "sampling", "coverage" })
|
|
.count(1)
|
|
.action([](parser_t& p) {
|
|
instr_mode = p.get<string_t>("mode");
|
|
if(instr_mode == "coverage" && !p.exists("coverage"))
|
|
coverage_mode = CODECOV_FUNCTION;
|
|
});
|
|
parser
|
|
.add_argument(
|
|
{ "-f", "--force" },
|
|
"Force the command-line argument configuration, i.e. don't get cute. Useful "
|
|
"for forcing runtime instrumentation of an executable that [A] Dyninst "
|
|
"thinks is a library after reading ELF and [B] whose name makes it look like "
|
|
"a library (e.g. starts with 'lib' and/or ends in '.so', '.so.*', or '.a')")
|
|
.max_count(1)
|
|
.action([](parser_t& p) { force_config = p.get<bool>("force"); });
|
|
|
|
if(_cmdc == 0)
|
|
{
|
|
parser
|
|
.add_argument({ "-c", "--command" },
|
|
"Input executable and arguments (if '-- <CMD>' not provided)")
|
|
.count(1)
|
|
.action([&](parser_t& p) {
|
|
auto keys = p.get<strvec_t>("c");
|
|
if(keys.empty())
|
|
{
|
|
p.print_help(extra_help);
|
|
std::exit(EXIT_FAILURE);
|
|
}
|
|
keys.at(0) = get_realpath(get_absolute_filepath(keys.at(0)));
|
|
mutname = keys.at(0);
|
|
_cmdc = keys.size();
|
|
_cmdv = new char*[_cmdc];
|
|
for(int i = 0; i < _cmdc; ++i)
|
|
copy_str(_cmdv[i], keys.at(i).c_str());
|
|
});
|
|
}
|
|
|
|
parser.add_argument({ "" }, "");
|
|
parser.add_argument({ "[LIBRARY OPTIONS]" }, "");
|
|
parser.add_argument({ "" }, "");
|
|
parser.add_argument({ "--prefer" }, "Prefer this library types when available")
|
|
.choices({ "shared", "static" })
|
|
.count(1)
|
|
.action([](parser_t& p) { prefer_library = p.get<string_t>("prefer"); });
|
|
parser
|
|
.add_argument(
|
|
{ "-L", "--library" },
|
|
TIMEMORY_JOIN("", "Libraries with instrumentation routines (default: \"",
|
|
inputlib.front(), "\")"))
|
|
.action([&inputlib](parser_t& p) { inputlib = p.get<strvec_t>("library"); });
|
|
parser
|
|
.add_argument({ "-m", "--main-function" },
|
|
"The primary function to instrument around, e.g. 'main'")
|
|
.count(1)
|
|
.action([](parser_t& p) { main_fname = p.get<string_t>("main-function"); });
|
|
parser
|
|
.add_argument({ "--load" },
|
|
"Supplemental instrumentation library names w/o extension (e.g. "
|
|
"'libinstr' for 'libinstr.so' or 'libinstr.a')")
|
|
.dtype("string")
|
|
.action([](parser_t& p) {
|
|
auto _load = p.get<strvec_t>("load");
|
|
for(const auto& itr : _load)
|
|
extra_libs.insert(itr);
|
|
});
|
|
parser
|
|
.add_argument({ "--load-instr" },
|
|
"Load {available,instrumented,excluded,overlapping}-instr JSON or "
|
|
"XML file(s) and override what is read from the binary")
|
|
.dtype("filepath")
|
|
.max_count(-1)
|
|
.action([](parser_t& p) {
|
|
auto _load = p.get<strvec_t>("load-instr");
|
|
std::map<std::string, fmodset_t*> module_function_map = {
|
|
{ "available_module_functions", &available_module_functions },
|
|
{ "instrumented_module_functions", &instrumented_module_functions },
|
|
{ "coverage_module_functions", &coverage_module_functions },
|
|
{ "excluded_module_functions", &excluded_module_functions },
|
|
{ "overlapping_module_functions", &overlapping_module_functions },
|
|
};
|
|
for(const auto& itr : _load)
|
|
load_info(itr, module_function_map, 0);
|
|
for(const auto& itr : module_function_map)
|
|
{
|
|
auto _empty = itr.second->empty();
|
|
if(!_empty)
|
|
verbprintf(0, "Loaded %zu module functions for %s\n",
|
|
itr.second->size(), itr.first.c_str());
|
|
fixed_module_functions.at(itr.second) = !_empty;
|
|
}
|
|
});
|
|
parser
|
|
.add_argument({ "--init-functions" },
|
|
"Initialization function(s) for supplemental instrumentation "
|
|
"libraries (see '--load' option)")
|
|
.dtype("string")
|
|
.action([](parser_t& p) { init_stub_names = p.get<strvec_t>("init-functions"); });
|
|
parser
|
|
.add_argument({ "--fini-functions" },
|
|
"Finalization function(s) for supplemental instrumentation "
|
|
"libraries (see '--load' option)")
|
|
.dtype("string")
|
|
.action([](parser_t& p) { fini_stub_names = p.get<strvec_t>("fini-functions"); });
|
|
parser
|
|
.add_argument(
|
|
{ "--all-functions" },
|
|
"When finding functions, include the functions which are not instrumentable. "
|
|
"This is purely diagnostic for the available/excluded functions output")
|
|
.dtype("boolean")
|
|
.max_count(1)
|
|
.action([](parser_t& p) { include_uninstr = p.get<bool>("all-functions"); });
|
|
|
|
parser.add_argument({ "" }, "");
|
|
parser.add_argument({ "[SYMBOL SELECTION OPTIONS]" }, "");
|
|
parser.add_argument({ "" }, "");
|
|
parser.add_argument({ "-I", "--function-include" },
|
|
"Regex(es) for including functions (despite heuristics)");
|
|
parser.add_argument({ "-E", "--function-exclude" },
|
|
"Regex(es) for excluding functions (always applied)");
|
|
parser.add_argument({ "-R", "--function-restrict" },
|
|
"Regex(es) for restricting functions only to those "
|
|
"that match the provided regular-expressions");
|
|
parser.add_argument({ "--caller-include" },
|
|
"Regex(es) for including functions that call the "
|
|
"listed functions (despite heuristics)");
|
|
parser.add_argument({ "-MI", "--module-include" },
|
|
"Regex(es) for selecting modules/files/libraries "
|
|
"(despite heuristics)");
|
|
parser.add_argument({ "-ME", "--module-exclude" },
|
|
"Regex(es) for excluding modules/files/libraries "
|
|
"(always applied)");
|
|
parser.add_argument({ "-MR", "--module-restrict" },
|
|
"Regex(es) for restricting modules/files/libraries only to those "
|
|
"that match the provided regular-expressions");
|
|
parser.add_argument({ "--internal-function-include" },
|
|
"Regex(es) for including functions which are (likely) utilized "
|
|
"by omnitrace itself. Use this option with care.");
|
|
parser.add_argument(
|
|
{ "--internal-module-include" },
|
|
"Regex(es) for including modules/libraries which are (likely) utilized "
|
|
"by omnitrace itself. Use this option with care.");
|
|
parser.add_argument(
|
|
{ "--instruction-exclude" },
|
|
"Regex(es) for excluding functions containing certain instructions");
|
|
|
|
parser
|
|
.add_argument({ "--internal-library-deps" },
|
|
"Treat the libraries linked to the internal libraries as internal "
|
|
"libraries. This increase the internal library processing time and "
|
|
"consume more memory (so use with care) but may be useful when the "
|
|
"application uses Boost libraries and Dyninst is dynamically "
|
|
"linked against the same boost libraries")
|
|
.min_count(0)
|
|
.max_count(1)
|
|
.dtype("boolean")
|
|
.action([](parser_t& p) {
|
|
include_internal_linked_libs = p.get<bool>("internal-library-deps");
|
|
});
|
|
|
|
auto _internal_libs = get_internal_basic_libs();
|
|
|
|
parser
|
|
.add_argument({ "--internal-library-append" },
|
|
"Append to the list of libraries which omnitrace treats as being "
|
|
"used internally, e.g. OmniTrace will find all the symbols in "
|
|
"this library and prevent them from being instrumented.")
|
|
.action([](parser_t& p) {
|
|
for(const auto& itr : p.get<strvec_t>("internal-library-append"))
|
|
get_internal_basic_libs().emplace(itr);
|
|
});
|
|
|
|
parser
|
|
.add_argument({ "--internal-library-remove" },
|
|
"Remove the specified libraries from being treated as being "
|
|
"used internally, e.g. OmniTrace will permit all the symbols in "
|
|
"these libraries to be eligible for instrumentation.")
|
|
.choices(_internal_libs)
|
|
.action([](parser_t& p) {
|
|
auto _remove = p.get<strset_t>("internal-library-remove");
|
|
auto& _internal = get_internal_basic_libs();
|
|
for(const auto& itr : _remove)
|
|
_internal.erase(itr);
|
|
});
|
|
|
|
using timemory::join::array_config;
|
|
using timemory::join::join;
|
|
|
|
auto available_linkage = std::vector<symbol_linkage_t>{};
|
|
auto available_visibility = std::vector<symbol_visibility_t>{};
|
|
|
|
for(int i = SL_UNKNOWN; i < SL_END_V; ++i)
|
|
available_linkage.emplace_back(static_cast<symbol_linkage_t>(i));
|
|
for(int i = SV_UNKNOWN; i < SV_END_V; ++i)
|
|
available_visibility.emplace_back(static_cast<symbol_visibility_t>(i));
|
|
|
|
auto _get_strvec = [](const auto& _inp) {
|
|
auto _ret = std::vector<std::string>{};
|
|
_ret.reserve(_inp.size());
|
|
for(const auto& itr : _inp)
|
|
_ret.emplace_back(std::to_string(itr));
|
|
return _ret;
|
|
};
|
|
|
|
parser
|
|
.add_argument({ "--linkage" },
|
|
join("",
|
|
"Only instrument functions with specified linkage (default: ",
|
|
join(array_config{ ", ", "", "" }, enabled_linkage), ")"))
|
|
.min_count(1)
|
|
.choices(available_linkage)
|
|
.set_default(_get_strvec(enabled_linkage))
|
|
.action([](parser_t& p) {
|
|
enabled_linkage.clear();
|
|
for(const auto& itr : p.get<std::set<std::string>>("linkage"))
|
|
enabled_linkage.emplace(from_string<symbol_linkage_t>(itr));
|
|
});
|
|
|
|
parser
|
|
.add_argument(
|
|
{ "--visibility" },
|
|
join("", "Only instrument functions with specified visibility (default: ",
|
|
join(array_config{ ", ", "", "" }, enabled_visibility), ")"))
|
|
.min_count(1)
|
|
.choices(available_visibility)
|
|
.set_default(_get_strvec(enabled_visibility))
|
|
.action([](parser_t& p) {
|
|
enabled_visibility.clear();
|
|
for(const auto& itr : p.get<std::set<std::string>>("visibility"))
|
|
enabled_visibility.emplace(from_string<symbol_visibility_t>(itr));
|
|
});
|
|
|
|
parser.add_argument({ "" }, "");
|
|
parser.add_argument({ "[RUNTIME OPTIONS]" }, "");
|
|
parser.add_argument({ "" }, "");
|
|
parser
|
|
.add_argument({ "--label" },
|
|
"Labeling info for functions. By default, just the function name "
|
|
"is recorded. Use these options to gain more information about the "
|
|
"function signature or location of the functions")
|
|
.choices({ "file", "line", "return", "args" })
|
|
.dtype("string")
|
|
.action([](parser_t& p) {
|
|
auto _labels = p.get<strvec_t>("label");
|
|
for(const auto& itr : _labels)
|
|
{
|
|
if(std::regex_match(itr, std::regex("file", std::regex_constants::icase)))
|
|
use_file_info = true;
|
|
else if(std::regex_match(
|
|
itr, std::regex("return", std::regex_constants::icase)))
|
|
use_return_info = true;
|
|
else if(std::regex_match(itr,
|
|
std::regex("args", std::regex_constants::icase)))
|
|
use_args_info = true;
|
|
else if(std::regex_match(itr,
|
|
std::regex("line", std::regex_constants::icase)))
|
|
use_line_info = true;
|
|
}
|
|
});
|
|
parser.add_argument()
|
|
.names({ "-C", "--config" })
|
|
.dtype("string")
|
|
.min_count(1)
|
|
.description("Read in a configuration file and encode these values as the "
|
|
"defaults in the executable");
|
|
parser.add_argument()
|
|
.names({ "-d", "--default-components" })
|
|
.dtype("string")
|
|
.description("Default components to instrument (only useful when timemory is "
|
|
"enabled in omnitrace library)")
|
|
.action([](parser_t& p) {
|
|
auto _components = p.get<strvec_t>("default-components");
|
|
default_components = {};
|
|
for(size_t i = 0; i < _components.size(); ++i)
|
|
{
|
|
if(_components.at(i) == "none")
|
|
{
|
|
default_components = "none";
|
|
break;
|
|
}
|
|
default_components += _components.at(i);
|
|
if(i + 1 < _components.size()) default_components += ",";
|
|
}
|
|
if(default_components == "none")
|
|
default_components = {};
|
|
else
|
|
{
|
|
auto _strcomp = p.get<std::string>("default-components");
|
|
if(!_strcomp.empty() && default_components.empty())
|
|
default_components = _strcomp;
|
|
}
|
|
});
|
|
parser.add_argument({ "--env" },
|
|
"Environment variables to add to the runtime in form "
|
|
"VARIABLE=VALUE. E.g. use '--env OMNITRACE_USE_TIMEMORY=ON' to "
|
|
"default to using timemory instead of perfetto");
|
|
parser
|
|
.add_argument({ "--mpi" },
|
|
"Enable MPI support (requires omnitrace built w/ full or partial "
|
|
"MPI support). NOTE: this will automatically be activated if "
|
|
"MPI_Init, MPI_Init_thread, MPI_Finalize, MPI_Comm_rank, or "
|
|
"MPI_Comm_size are found in the symbol table of target")
|
|
.max_count(1)
|
|
.action([](parser_t& p) {
|
|
use_mpi = p.get<bool>("mpi");
|
|
#if OMNITRACE_USE_MPI == 0 && OMNITRACE_USE_MPI_HEADERS == 0
|
|
errprintf(0, "omnitrace was not built with full or partial MPI support\n");
|
|
use_mpi = false;
|
|
#endif
|
|
});
|
|
|
|
parser.add_argument({ "" }, "");
|
|
parser.add_argument({ "[GRANULARITY OPTIONS]" }, "");
|
|
parser.add_argument({ "" }, "");
|
|
parser.add_argument({ "-l", "--instrument-loops" }, "Instrument at the loop level")
|
|
.dtype("boolean")
|
|
.max_count(1)
|
|
.action([](parser_t& p) { loop_level_instr = p.get<bool>("instrument-loops"); });
|
|
parser
|
|
.add_argument({ "-i", "--min-instructions" },
|
|
"If the number of instructions in a function is less than this "
|
|
"value, exclude it from instrumentation")
|
|
.count(1)
|
|
.dtype("int")
|
|
.action(
|
|
[](parser_t& p) { min_instructions = p.get<size_t>("min-instructions"); });
|
|
parser
|
|
.add_argument({ "-r", "--min-address-range" },
|
|
"If the address range of a function is less than this value, "
|
|
"exclude it from instrumentation")
|
|
.count(1)
|
|
.dtype("int")
|
|
.action(
|
|
[](parser_t& p) { min_address_range = p.get<size_t>("min-address-range"); });
|
|
parser
|
|
.add_argument({ "--min-instructions-loop" },
|
|
"If the number of instructions in a function containing a loop is "
|
|
"less than this value, exclude it from instrumentation")
|
|
.count(1)
|
|
.dtype("int")
|
|
.action([](parser_t& p) {
|
|
min_loop_instructions = p.get<size_t>("min-instructions-loop");
|
|
});
|
|
parser
|
|
.add_argument({ "--min-address-range-loop" },
|
|
"If the address range of a function containing a loop is less than "
|
|
"this value, exclude it from instrumentation")
|
|
.count(1)
|
|
.dtype("int")
|
|
.action([](parser_t& p) {
|
|
min_loop_address_range = p.get<size_t>("min-address-range-loop");
|
|
});
|
|
parser
|
|
.add_argument(
|
|
{ "--coverage" },
|
|
"Enable recording the code coverage. If instrumenting in coverage mode ('-M "
|
|
"converage'), this simply specifies the granularity. If instrumenting in "
|
|
"trace or sampling mode, this enables recording code-coverage in addition to "
|
|
"the instrumentation of that mode (if any).")
|
|
.max_count(1)
|
|
.choices({ "none", "function", "basic_block" })
|
|
.action([](parser_t& p) {
|
|
auto _v = p.get<std::string>("coverage");
|
|
if(_v == "function" || _v.empty())
|
|
coverage_mode = CODECOV_FUNCTION;
|
|
else if(_v == "basic_block")
|
|
coverage_mode = CODECOV_BASIC_BLOCK;
|
|
else
|
|
coverage_mode = CODECOV_NONE;
|
|
});
|
|
parser
|
|
.add_argument({ "--dynamic-callsites" },
|
|
"Force instrumentation if a function has dynamic callsites (e.g. "
|
|
"function pointers)")
|
|
.max_count(1)
|
|
.dtype("boolean")
|
|
.action([](parser_t& p) {
|
|
instr_dynamic_callsites = p.get<bool>("dynamic-callsites");
|
|
});
|
|
parser
|
|
.add_argument(
|
|
{ "--traps" },
|
|
"Instrument points which require using a trap. On the x86 architecture, "
|
|
"because instructions are of variable size, the instruction at a point may "
|
|
"be too small for Dyninst to replace it with the normal code sequence used "
|
|
"to call instrumentation. Also, when instrumentation is placed at points "
|
|
"other than subroutine entry, exit, or call points, traps may be used to "
|
|
"ensure the instrumentation fits. In this case, Dyninst replaces the "
|
|
"instruction with a single-byte instruction that generates a trap.")
|
|
.max_count(1)
|
|
.dtype("boolean")
|
|
.set_default(instr_traps)
|
|
.action([](parser_t& p) { instr_traps = p.get<bool>("traps"); });
|
|
parser
|
|
.add_argument({ "--loop-traps" },
|
|
"Instrument points within a loop which require using a trap (only "
|
|
"relevant when --instrument-loops is enabled).")
|
|
.max_count(1)
|
|
.dtype("boolean")
|
|
.set_default(instr_loop_traps)
|
|
.action([](parser_t& p) { instr_loop_traps = p.get<bool>("loop-traps"); });
|
|
parser
|
|
.add_argument(
|
|
{ "--allow-overlapping" },
|
|
"Allow dyninst to instrument either multiple functions which overlap (share "
|
|
"part of same function body) or single functions with multiple entry points. "
|
|
"For more info, see Section 2 of the DyninstAPI documentation.")
|
|
.max_count(1)
|
|
.action(
|
|
[](parser_t& p) { allow_overlapping = p.get<bool>("allow-overlapping"); });
|
|
parser
|
|
.add_argument(
|
|
{ "--parse-all-modules" },
|
|
"By default, omnitrace simply requests Dyninst to provide all the procedures "
|
|
"in the application image. If this option is enabled, omnitrace will iterate "
|
|
"over all the modules and extract the functions. Theoretically, it should be "
|
|
"the same but the data is slightly different, possibly due to weak binding "
|
|
"scopes. In general, enabling option will probably have no visible effect")
|
|
.max_count(1)
|
|
.action(
|
|
[](parser_t& p) { parse_all_modules = p.get<bool>("parse-all-modules"); });
|
|
|
|
parser.add_argument({ "" }, "");
|
|
parser.add_argument({ "[DYNINST OPTIONS]" }, "");
|
|
parser.add_argument({ "" }, "");
|
|
parser
|
|
.add_argument(
|
|
{ "-b", "--batch-size" },
|
|
"Dyninst supports batch insertion of multiple points during runtime "
|
|
"instrumentation. If one large batch "
|
|
"insertion fails, this value will be used to create smaller batches. Larger "
|
|
"batches generally decrease the instrumentation time")
|
|
.count(1)
|
|
.dtype("int")
|
|
.action([](parser_t& p) { batch_size = p.get<size_t>("batch-size"); });
|
|
parser.add_argument({ "--dyninst-rt" }, "Path(s) to the dyninstAPI_RT library")
|
|
.dtype("filepath")
|
|
.min_count(1)
|
|
.action([](parser_t& _p) {
|
|
auto _v = _p.get<strvec_t>("dyninst-rt");
|
|
std::copy(_dyn_api_rt_paths.begin(), _dyn_api_rt_paths.end(),
|
|
std::back_inserter(_v));
|
|
std::swap(_dyn_api_rt_paths, _v);
|
|
});
|
|
parser
|
|
.add_argument({ "--dyninst-options" },
|
|
"Advanced dyninst options: BPatch::set<OPTION>(bool), e.g. "
|
|
"bpatch->setTrampRecursive(true)")
|
|
.choices({ "TypeChecking", "SaveFPR", "DebugParsing", "DelayedParsing",
|
|
"InstrStackFrames", "TrampRecursive", "MergeTramp",
|
|
"BaseTrampDeletion" });
|
|
|
|
auto err = parser.parse(_argc, _argv);
|
|
|
|
if(parser.exists("h") || parser.exists("help"))
|
|
{
|
|
parser.print_help(extra_help);
|
|
return 0;
|
|
}
|
|
|
|
verbprintf(0, "\n");
|
|
verbprintf(0, "command :: '%s'...\n", cmd_string(_cmdc, _cmdv).c_str());
|
|
verbprintf(0, "\n");
|
|
|
|
if(err)
|
|
{
|
|
std::cerr << err << std::endl;
|
|
parser.print_help(extra_help);
|
|
return -1;
|
|
}
|
|
|
|
if(parser.exists("config"))
|
|
{
|
|
struct omnitrace_env_config_s
|
|
{};
|
|
auto _configs = parser.get<strvec_t>("config");
|
|
for(auto&& itr : _configs)
|
|
{
|
|
auto _settings = tim::settings::push<omnitrace_env_config_s>();
|
|
for(auto&& iitr : *_settings)
|
|
{
|
|
if(iitr.second->get_updated()) iitr.second->set_user_updated();
|
|
}
|
|
_settings->read(itr);
|
|
for(auto&& iitr : *_settings)
|
|
{
|
|
if(iitr.second && iitr.second->get_config_updated())
|
|
{
|
|
env_config_variables.emplace_back(TIMEMORY_JOIN(
|
|
'=', iitr.second->get_env_name(), iitr.second->as_string()));
|
|
verbprintf(1, "Exporting known config value :: %s\n",
|
|
env_config_variables.back().c_str());
|
|
}
|
|
}
|
|
for(auto&& iitr : _settings->get_unknown_configs())
|
|
{
|
|
env_config_variables.emplace_back(
|
|
TIMEMORY_JOIN('=', iitr.first, iitr.second));
|
|
verbprintf(1, "Exporting unknown config value :: %s\n",
|
|
env_config_variables.back().c_str());
|
|
}
|
|
tim::settings::pop<omnitrace_env_config_s>();
|
|
}
|
|
}
|
|
|
|
auto _handle_heuristics = [&parser](std::string&& _exists, std::string&& _not_exists,
|
|
auto& _field, auto _value, std::string&& _msg,
|
|
bool _cond) {
|
|
// if first is specified but second is not, to reduce verbosity of command-line
|
|
// and increase simplicity, set _field to specified value
|
|
if(parser.exists(_exists) && !parser.exists(_not_exists) && _cond)
|
|
{
|
|
verbprintf(3,
|
|
"Option '--%s' specified but '--%s <N>' was not specified. "
|
|
"Setting %s to %s...\n",
|
|
_exists.c_str(), _not_exists.c_str(), _msg.c_str(),
|
|
TIMEMORY_JOIN("", _value).c_str());
|
|
_field = _value;
|
|
}
|
|
};
|
|
|
|
// if instructions was specified and address range was not
|
|
_handle_heuristics("min-instructions", "min-address-range", min_address_range, 0,
|
|
"minimum address range", true);
|
|
_handle_heuristics("min-instructions-loop", "min-address-range-loop",
|
|
min_loop_address_range, 0, "minimum address range for loops",
|
|
true);
|
|
|
|
// if address range was specified but instructions was not
|
|
_handle_heuristics("min-address-range", "min-instructions", min_instructions, 0,
|
|
"minimum instructions", true);
|
|
_handle_heuristics("min-address-range-loop", "min-instructions-loop",
|
|
min_loop_instructions, 0, "minimum instructions for loops", true);
|
|
|
|
// if non-loop value was specified but loop value was not
|
|
_handle_heuristics("min-instructions", "min-instructions-loop", min_loop_instructions,
|
|
min_instructions, "minimum instructions for loops", true);
|
|
_handle_heuristics("min-address-range", "min-address-range-loop",
|
|
min_loop_address_range, min_address_range,
|
|
"minimum address range for loops", true);
|
|
|
|
// if non-loop instructions was specified and loop address range was not specified
|
|
// as long as non-loop address range and loop instructions were not specified
|
|
_handle_heuristics("min-instructions", "min-address-range-loop",
|
|
min_loop_address_range, 0, "minimum address range for loops",
|
|
!parser.exists("min-address-range") &&
|
|
!parser.exists("min-instructions-loop"));
|
|
|
|
// if non-loop address range was specified and loop instructions was not specified
|
|
// as long as non-loop instructions and loop address range were not specified
|
|
_handle_heuristics("min-address-range", "min-instructions-loop",
|
|
min_loop_instructions, 0, "minimum instructions for loops",
|
|
!parser.exists("min-instructions") &&
|
|
!parser.exists("min-address-range-loop"));
|
|
|
|
auto _omnitrace_exe_path = tim::dirname(::get_realpath("/proc/self/exe"));
|
|
verbprintf(4, "omnitrace exe path: %s\n", _omnitrace_exe_path.c_str());
|
|
|
|
if(_cmdv && _cmdv[0] && strlen(_cmdv[0]) > 0)
|
|
{
|
|
auto _is_executable = omnitrace_get_is_executable(_cmdv[0], binary_rewrite);
|
|
std::string _cmdv_base = ::basename(_cmdv[0]);
|
|
auto _has_lib_suffix = _cmdv_base.length() > 3 &&
|
|
(_cmdv_base.find(".so.") != std::string::npos ||
|
|
_cmdv_base.find(".so") == (_cmdv_base.length() - 3) ||
|
|
_cmdv_base.find(".a") == (_cmdv_base.length() - 2));
|
|
auto _has_lib_prefix = _cmdv_base.length() > 3 && _cmdv_base.find("lib") == 0;
|
|
if(!force_config && !_is_executable && !binary_rewrite &&
|
|
(_has_lib_prefix || _has_lib_suffix))
|
|
{
|
|
fflush(stdout);
|
|
std::stringstream _separator{};
|
|
// 18 is approximate length of '[omnitrace][exe] '
|
|
// 32 is approximate length of 'Warning! "" is not executable!'
|
|
size_t _width =
|
|
std::min<size_t>(std::get<0>(tim::utility::console::get_columns()) - 18,
|
|
strlen(_cmdv[0]) + 32);
|
|
_separator.fill('=');
|
|
_separator << "#" << std::setw(_width - 2) << ""
|
|
<< "#";
|
|
verbprintf(0, "%s\n", _separator.str().c_str());
|
|
verbprintf(0, "\n");
|
|
verbprintf(0, "Warning! '%s' is not executable!\n", _cmdv[0]);
|
|
verbprintf(0, "Runtime instrumentation is not possible!\n");
|
|
verbprintf(0, "Switching to binary rewrite mode and assuming '--simulate "
|
|
"--all-functions'\n");
|
|
verbprintf(
|
|
0, "(which will provide an approximation for runtime instrumentation)\n");
|
|
verbprintf(1, "%s :: (^lib)=%s, (.so$|.a$|.so.*)=%s\n", _cmdv_base.c_str(),
|
|
(_has_lib_prefix) ? "true" : "false",
|
|
(_has_lib_suffix) ? "true" : "false");
|
|
verbprintf(0, "\n");
|
|
verbprintf(0, "%s\n", _separator.str().c_str());
|
|
verbprintf(0, "\n");
|
|
fflush(stdout);
|
|
std::this_thread::sleep_for(std::chrono::milliseconds{ 500 });
|
|
binary_rewrite = true;
|
|
simulate = true;
|
|
include_uninstr = true;
|
|
}
|
|
}
|
|
|
|
if(binary_rewrite && outfile.empty())
|
|
{
|
|
auto _is_local = (get_realpath(cmdv0) ==
|
|
TIMEMORY_JOIN('/', get_cwd(), ::basename(cmdv0.c_str())));
|
|
auto _cmd = std::string{ ::basename(cmdv0.c_str()) };
|
|
if(_cmd.find('.') == std::string::npos)
|
|
{
|
|
// there is no extension, assume it is an exe
|
|
outfile = (_is_local) ? TIMEMORY_JOIN('.', _cmd, "inst") : _cmd;
|
|
}
|
|
else if(_cmd.find("lib") == 0 || _cmd.find(".so") != std::string::npos ||
|
|
_cmd.find(".a") == _cmd.length() - 2)
|
|
{
|
|
// if it starts with lib, ends with .a, or contains .so (e.g. libfoo.so,
|
|
// libfoo.so.2), assume it is a library and retain the name but put it in a
|
|
// different directory
|
|
outfile = (_is_local) ? TIMEMORY_JOIN('/', "instrumented", _cmd) : _cmd;
|
|
}
|
|
else
|
|
{
|
|
outfile = (_is_local) ? TIMEMORY_JOIN('.', _cmd, "inst") : _cmd;
|
|
}
|
|
verbprintf(0,
|
|
"Binary rewrite was activated via '-o' but no filename was provided. "
|
|
"Using: '%s'\n",
|
|
outfile.c_str());
|
|
}
|
|
|
|
if(binary_rewrite)
|
|
{
|
|
auto* _save = _cmdv[0];
|
|
_cmdv[0] = const_cast<char*>(outfile.c_str());
|
|
tim::timemory_init(_cmdc, _cmdv, "omnitrace-");
|
|
_cmdv[0] = _save;
|
|
}
|
|
else
|
|
{
|
|
tim::timemory_init(_cmdc, _cmdv, "omnitrace-");
|
|
}
|
|
|
|
if(!logfile.empty())
|
|
{
|
|
log_ofs = std::make_unique<std::ofstream>();
|
|
verbprintf_bare(0, "%s", ::tim::log::color::source());
|
|
verbprintf(0, "Opening '%s' for log output... ", logfile.c_str());
|
|
if(!filepath::open(*log_ofs, logfile))
|
|
throw std::runtime_error(JOIN(" ", "Error opening log output file", logfile));
|
|
verbprintf_bare(0, "Done\n%s", ::tim::log::color::end());
|
|
print_log_entries(*log_ofs, -1, {}, {}, "", false);
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------//
|
|
//
|
|
// REGEX OPTIONS
|
|
//
|
|
//----------------------------------------------------------------------------------//
|
|
//
|
|
{
|
|
// Helper function for adding regex expressions
|
|
auto add_regex = [](auto& regex_array, const string_t& regex_expr) {
|
|
OMNITRACE_ADD_DETAILED_LOG_ENTRY("", "Adding regular expression \"",
|
|
regex_expr, "\" to regex_array@",
|
|
®ex_array);
|
|
if(!regex_expr.empty())
|
|
regex_array.emplace_back(std::regex(regex_expr, regex_opts));
|
|
};
|
|
|
|
add_regex(func_include, tim::get_env<string_t>("OMNITRACE_REGEX_INCLUDE", ""));
|
|
add_regex(func_exclude, tim::get_env<string_t>("OMNITRACE_REGEX_EXCLUDE", ""));
|
|
add_regex(func_restrict, tim::get_env<string_t>("OMNITRACE_REGEX_RESTRICT", ""));
|
|
add_regex(caller_include,
|
|
tim::get_env<string_t>("OMNITRACE_REGEX_CALLER_INCLUDE"));
|
|
add_regex(func_internal_include,
|
|
tim::get_env<string_t>("OMNITRACE_REGEX_INTERNAL_INCLUDE", ""));
|
|
|
|
add_regex(file_include,
|
|
tim::get_env<string_t>("OMNITRACE_REGEX_MODULE_INCLUDE", ""));
|
|
add_regex(file_exclude,
|
|
tim::get_env<string_t>("OMNITRACE_REGEX_MODULE_EXCLUDE", ""));
|
|
add_regex(file_restrict,
|
|
tim::get_env<string_t>("OMNITRACE_REGEX_MODULE_RESTRICT", ""));
|
|
add_regex(file_internal_include,
|
|
tim::get_env<string_t>("OMNITRACE_REGEX_MODULE_INTERNAL_INCLUDE", ""));
|
|
|
|
add_regex(instruction_exclude,
|
|
tim::get_env<string_t>("OMNITRACE_REGEX_INSTRUCTION_EXCLUDE", ""));
|
|
|
|
// Helper function for parsing the regex options
|
|
auto _parse_regex_option = [&parser, &add_regex](const string_t& _option,
|
|
regexvec_t& _regex_vec) {
|
|
if(parser.exists(_option))
|
|
{
|
|
auto keys = parser.get<strvec_t>(_option);
|
|
for(const auto& itr : keys)
|
|
add_regex(_regex_vec, itr);
|
|
}
|
|
};
|
|
|
|
_parse_regex_option("function-include", func_include);
|
|
_parse_regex_option("function-exclude", func_exclude);
|
|
_parse_regex_option("function-restrict", func_restrict);
|
|
_parse_regex_option("caller-include", caller_include);
|
|
_parse_regex_option("internal-function-include", func_internal_include);
|
|
_parse_regex_option("module-include", file_include);
|
|
_parse_regex_option("module-exclude", file_exclude);
|
|
_parse_regex_option("module-restrict", file_restrict);
|
|
_parse_regex_option("internal-module-include", file_internal_include);
|
|
_parse_regex_option("instruction-exclude", instruction_exclude);
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------//
|
|
//
|
|
// DYNINST OPTIONS
|
|
//
|
|
//----------------------------------------------------------------------------------//
|
|
|
|
for(const auto& itr : _dyn_api_rt_paths)
|
|
{
|
|
lib_search_paths.emplace_back(itr);
|
|
lib_search_paths.emplace_back(filepath::dirname(itr));
|
|
}
|
|
|
|
find_dyn_api_rt();
|
|
|
|
int dyninst_verb = 2;
|
|
if(parser.exists("dyninst-options"))
|
|
{
|
|
dyninst_defs = parser.get<std::set<std::string>>("dyninst-options");
|
|
dyninst_verb = 0;
|
|
}
|
|
|
|
auto get_dyninst_option = [&](const std::string& _opt) {
|
|
bool _ret = dyninst_defs.find(_opt) != dyninst_defs.end();
|
|
verbprintf(dyninst_verb, "[dyninst-option]> %-20s = %4s\n", _opt.c_str(),
|
|
(_ret) ? "on" : "off");
|
|
return _ret;
|
|
};
|
|
|
|
bpatch = std::make_shared<patch_t>();
|
|
bpatch->setTypeChecking(true);
|
|
bpatch->setSaveFPR(true);
|
|
bpatch->setDelayedParsing(true);
|
|
bpatch->setDebugParsing(true);
|
|
bpatch->setInstrStackFrames(false);
|
|
bpatch->setLivenessAnalysis(false);
|
|
bpatch->setBaseTrampDeletion(false);
|
|
bpatch->setTrampRecursive(false);
|
|
bpatch->setMergeTramp(true);
|
|
|
|
bpatch->setTypeChecking(get_dyninst_option("TypeChecking"));
|
|
bpatch->setSaveFPR(get_dyninst_option("SaveFPR"));
|
|
bpatch->setDelayedParsing(get_dyninst_option("DelayedParsing"));
|
|
|
|
bpatch->setDebugParsing(get_dyninst_option("DebugParsing"));
|
|
bpatch->setInstrStackFrames(get_dyninst_option("InstrStackFrames"));
|
|
bpatch->setTrampRecursive(get_dyninst_option("TrampRecursive"));
|
|
bpatch->setMergeTramp(get_dyninst_option("MergeTramp"));
|
|
bpatch->setBaseTrampDeletion(get_dyninst_option("BaseTrampDeletion"));
|
|
|
|
//----------------------------------------------------------------------------------//
|
|
//
|
|
// MAIN
|
|
//
|
|
//----------------------------------------------------------------------------------//
|
|
|
|
if(_cmdc == 0)
|
|
{
|
|
parser.print_help(extra_help);
|
|
fprintf(stderr, "\nError! No command for dynamic instrumentation. Use "
|
|
"\n\tomnitrace <OPTIONS> -- <COMMAND> <ARGS>\nE.g. "
|
|
"\n\tomnitrace -o foo.inst -- ./foo\nwill output an "
|
|
"instrumented version of 'foo' executable to 'foo.inst'\n");
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
verbprintf(1, "instrumentation target: %s\n", mutname.c_str());
|
|
|
|
// did we load a library? if not, load the default
|
|
auto generate_libnames = [](auto& _targ, const auto& _base,
|
|
const std::set<string_t>& _ext) {
|
|
for(const auto& bitr : _base)
|
|
for(const auto& eitr : _ext)
|
|
{
|
|
_targ.emplace_back(bitr + eitr);
|
|
}
|
|
};
|
|
|
|
generate_libnames(libname, inputlib, { "" });
|
|
generate_libnames(sharedlibname, inputlib, { ".so" });
|
|
generate_libnames(staticlibname, inputlib, { ".a" });
|
|
|
|
// Register a callback function that prints any error messages
|
|
bpatch->registerErrorCallback(error_func_real);
|
|
|
|
//----------------------------------------------------------------------------------//
|
|
//
|
|
// Start the instrumentation procedure by opening a file for binary editing,
|
|
// attaching to a running process, or starting a process
|
|
//
|
|
//----------------------------------------------------------------------------------//
|
|
|
|
addr_space =
|
|
omnitrace_get_address_space(bpatch, _cmdc, _cmdv, binary_rewrite, _pid, mutname);
|
|
|
|
// addr_space->allowTraps(instr_traps);
|
|
|
|
if(!addr_space)
|
|
{
|
|
errprintf(-1, "address space for dynamic instrumentation was not created\n");
|
|
}
|
|
|
|
if(dynamic_cast<binary_edit_t*>(addr_space) != nullptr &&
|
|
dynamic_cast<process_t*>(addr_space) != nullptr)
|
|
{
|
|
errprintf(-1, "address space statisfied dynamic_cast<%s> and dynamic_cast<%s>.\n",
|
|
tim::demangle<binary_edit_t*>().c_str(),
|
|
tim::demangle<process_t*>().c_str());
|
|
}
|
|
|
|
auto _rewrite = (dynamic_cast<binary_edit_t*>(addr_space) != nullptr &&
|
|
dynamic_cast<process_t*>(addr_space) == nullptr);
|
|
if(_rewrite != binary_rewrite)
|
|
{
|
|
errprintf(-1, "binary rewrite was %s but has been deduced to be %s\n",
|
|
(binary_rewrite) ? "ON" : "OFF", (_rewrite) ? "ON" : "OFF");
|
|
}
|
|
|
|
process_t* app_thread = nullptr;
|
|
binary_edit_t* app_binary = nullptr;
|
|
|
|
// get image
|
|
verbprintf(1, "Getting the address space image, modules, and procedures...\n");
|
|
image_t* app_image = addr_space->getImage();
|
|
std::vector<module_t*>* app_modules = app_image->getModules();
|
|
std::vector<procedure_t*>* app_functions = app_image->getProcedures(include_uninstr);
|
|
std::set<module_t*> modules = {};
|
|
std::set<procedure_t*> functions = {};
|
|
|
|
if(app_modules) process_modules(*app_modules);
|
|
|
|
//----------------------------------------------------------------------------------//
|
|
//
|
|
// Generate a log of all the available procedures and modules
|
|
//
|
|
//----------------------------------------------------------------------------------//
|
|
std::set<std::string> module_names = {};
|
|
|
|
static auto _insert_module_function = [](fmodset_t& _module_funcs, auto _v) {
|
|
if(!fixed_module_functions.at(&_module_funcs)) _module_funcs.emplace(_v);
|
|
};
|
|
|
|
auto _add_overlapping = [](module_t* mitr, procedure_t* pitr) {
|
|
OMNITRACE_ADD_LOG_ENTRY("Checking if procedure", get_name(pitr), "in module",
|
|
get_name(mitr), "is overlapping");
|
|
if(!pitr->isInstrumentable()) return;
|
|
std::vector<procedure_t*> _overlapping{};
|
|
if(pitr->findOverlapping(_overlapping))
|
|
{
|
|
OMNITRACE_ADD_LOG_ENTRY("Adding overlapping procedure", get_name(pitr),
|
|
"and module", get_name(mitr));
|
|
_insert_module_function(overlapping_module_functions,
|
|
module_function{ mitr, pitr });
|
|
for(auto* oitr : _overlapping)
|
|
{
|
|
if(!oitr->isInstrumentable()) continue;
|
|
_insert_module_function(overlapping_module_functions,
|
|
module_function{ oitr->getModule(), oitr });
|
|
}
|
|
}
|
|
};
|
|
|
|
if(app_functions && !app_functions->empty())
|
|
{
|
|
for(auto* itr : *app_functions)
|
|
{
|
|
if(itr->getModule())
|
|
{
|
|
functions.emplace(itr);
|
|
modules.emplace(itr->getModule());
|
|
}
|
|
}
|
|
verbprintf(2, "Adding %zu procedures found in the app image...\n",
|
|
functions.size());
|
|
for(auto* itr : functions)
|
|
{
|
|
if(itr->isInstrumentable() || (simulate && include_uninstr))
|
|
{
|
|
module_t* mod = itr->getModule();
|
|
auto _modfn = module_function{ mod, itr };
|
|
module_names.insert(_modfn.module_name);
|
|
_insert_module_function(available_module_functions, _modfn);
|
|
_add_overlapping(mod, itr);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
verbprintf(
|
|
0, "Warning! No functions in application. Enabling parsing all modules...\n");
|
|
parse_all_modules = true;
|
|
}
|
|
|
|
if(parse_all_modules && app_modules && !app_modules->empty())
|
|
{
|
|
for(auto* itr : *app_modules)
|
|
modules.emplace(itr);
|
|
|
|
verbprintf(2,
|
|
"Adding the procedures from %zu modules found in the app image...\n",
|
|
modules.size());
|
|
for(auto* itr : modules)
|
|
{
|
|
auto* procedures = itr->getProcedures(include_uninstr);
|
|
if(procedures)
|
|
{
|
|
verbprintf(2, "Processing %zu procedures found in the %s module...\n",
|
|
procedures->size(), get_name(itr).data());
|
|
for(auto* pitr : *procedures)
|
|
{
|
|
if(!pitr->isInstrumentable() && !simulate && !include_uninstr)
|
|
continue;
|
|
functions.emplace(pitr);
|
|
auto _modfn = module_function{ itr, pitr };
|
|
module_names.insert(_modfn.module_name);
|
|
_insert_module_function(available_module_functions, _modfn);
|
|
_add_overlapping(itr, pitr);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if(parse_all_modules)
|
|
{
|
|
verbprintf(0, "Warning! No modules in application...\n");
|
|
}
|
|
|
|
verbprintf(1, "\n");
|
|
verbprintf(1, "Found %zu functions in %zu modules in instrumentation target\n",
|
|
functions.size(), modules.size());
|
|
|
|
if(debug_print || verbose_level > 2)
|
|
{
|
|
module_function::reset_width();
|
|
for(const auto& itr : available_module_functions)
|
|
module_function::update_width(itr);
|
|
|
|
auto mwid = module_function::get_width().at(0);
|
|
mwid = std::max<size_t>(mwid, 15);
|
|
mwid = std::min<size_t>(mwid, 90);
|
|
auto ncol = 180 / std::min<size_t>(mwid, 180);
|
|
std::cout << "### MODULES ###\n| ";
|
|
for(size_t i = 0; i < module_names.size(); ++i)
|
|
{
|
|
auto itr = module_names.begin();
|
|
std::advance(itr, i);
|
|
std::string _v = *itr;
|
|
if(_v.length() >= mwid)
|
|
{
|
|
auto _resume = _v.length() - mwid + 15;
|
|
_v = _v.substr(0, 12) + "..." + _v.substr(_resume);
|
|
}
|
|
std::cout << std::setw(mwid) << _v << " | ";
|
|
if(i % ncol == ncol - 1) std::cout << "\n| ";
|
|
}
|
|
std::cout << '\n' << std::endl;
|
|
}
|
|
|
|
dump_info("available", available_module_functions, 1, werror, "available",
|
|
print_formats);
|
|
dump_info("overlapping", overlapping_module_functions, 1, werror,
|
|
"overlapping_module_functions", print_formats);
|
|
|
|
//----------------------------------------------------------------------------------//
|
|
//
|
|
// Get the derived type of the address space
|
|
//
|
|
//----------------------------------------------------------------------------------//
|
|
|
|
is_static_exe = addr_space->isStaticExecutable();
|
|
|
|
OMNITRACE_ADD_LOG_ENTRY("address space is", (is_static_exe) ? "" : "not",
|
|
"a static executable");
|
|
if(binary_rewrite)
|
|
app_binary = static_cast<BPatch_binaryEdit*>(addr_space);
|
|
else
|
|
app_thread = static_cast<BPatch_process*>(addr_space);
|
|
|
|
is_attached = (_pid >= 0 && app_thread != nullptr);
|
|
|
|
OMNITRACE_ADD_LOG_ENTRY("address space is attached:", is_attached);
|
|
|
|
if(!app_binary && !app_thread)
|
|
{
|
|
errprintf(-1, "No application thread or binary! nullptr to BPatch_binaryEdit* "
|
|
"and BPatch_process*\n")
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------//
|
|
//
|
|
// Helper functions for library stuff
|
|
//
|
|
//----------------------------------------------------------------------------------//
|
|
|
|
auto load_library = [addr_space](const std::vector<string_t>& _libnames) {
|
|
bool result = false;
|
|
// track the tried library names
|
|
string_t _tried_libs;
|
|
for(auto _libname : _libnames)
|
|
{
|
|
OMNITRACE_ADD_LOG_ENTRY("Getting the absolute lib filepath to", _libname);
|
|
_libname = get_realpath(get_absolute_lib_filepath(_libname));
|
|
_tried_libs += string_t("|") + _libname;
|
|
verbprintf(1, "loading library: '%s'...\n", _libname.c_str());
|
|
result = (addr_space->loadLibrary(_libname.c_str()) != nullptr);
|
|
verbprintf(2, "loadLibrary(%s) result = %s\n", _libname.c_str(),
|
|
(result) ? "success" : "failure");
|
|
if(result)
|
|
{
|
|
OMNITRACE_ADD_LOG_ENTRY("Using library:", _libname);
|
|
break;
|
|
}
|
|
}
|
|
if(!result)
|
|
{
|
|
errprintf(-127,
|
|
"Error: 'loadLibrary(%s)' failed.\nPlease ensure that the "
|
|
"library directory is in LD_LIBRARY_PATH environment variable "
|
|
"or absolute path is provided\n",
|
|
_tried_libs.substr(1).c_str());
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
};
|
|
|
|
auto get_library_ext = [=](const std::vector<string_t>& linput) {
|
|
auto lnames = linput;
|
|
auto _get_library_ext = [](string_t lname) {
|
|
if(lname.find(".so") != string_t::npos ||
|
|
lname.find(".a") == lname.length() - 2)
|
|
return lname;
|
|
if(!prefer_library.empty())
|
|
return (lname +
|
|
((prefer_library == "static" || is_static_exe) ? ".a" : ".so"));
|
|
else
|
|
return (lname + ((is_static_exe) ? ".a" : ".so"));
|
|
};
|
|
for(auto& lname : lnames)
|
|
lname = _get_library_ext(lname);
|
|
OMNITRACE_ADD_LOG_ENTRY("Using library:", lnames);
|
|
return lnames;
|
|
};
|
|
|
|
//----------------------------------------------------------------------------------//
|
|
//
|
|
// find _init and _fini before loading instrumentation library!
|
|
// These will be used for initialization and finalization if main is not found
|
|
//
|
|
//----------------------------------------------------------------------------------//
|
|
|
|
auto* main_func = find_function(app_image, main_fname.c_str());
|
|
auto* user_start_func = find_function(app_image, "omnitrace_user_start_trace",
|
|
{ "omnitrace_user_start_thread_trace" });
|
|
auto* user_stop_func = find_function(app_image, "omnitrace_user_stop_trace",
|
|
{ "omnitrace_user_stop_thread_trace" });
|
|
#if OMNITRACE_USE_MPI > 0 || OMNITRACE_USE_MPI_HEADERS > 0
|
|
// if any of the below MPI functions are found, enable MPI support
|
|
for(const auto* itr : { "MPI_Init", "MPI_Init_thread", "MPI_Finalize",
|
|
"MPI_Comm_rank", "MPI_Comm_size" })
|
|
{
|
|
if(find_function(app_image, itr) != nullptr)
|
|
{
|
|
verbprintf(0, "Found '%s' in '%s'. Enabling MPI support...\n", itr, _cmdv[0]);
|
|
use_mpi = true;
|
|
break;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
//----------------------------------------------------------------------------------//
|
|
//
|
|
// Load the instrumentation libraries
|
|
//
|
|
//----------------------------------------------------------------------------------//
|
|
|
|
load_library(get_library_ext(libname));
|
|
|
|
for(const auto& itr : extra_libs)
|
|
load_library(get_library_ext({ itr }));
|
|
|
|
//----------------------------------------------------------------------------------//
|
|
//
|
|
// Find the primary functions that will be used for instrumentation
|
|
//
|
|
//----------------------------------------------------------------------------------//
|
|
|
|
verbprintf(0, "Finding instrumentation functions...\n");
|
|
|
|
auto* init_func = find_function(app_image, "omnitrace_init");
|
|
auto* fini_func = find_function(app_image, "omnitrace_finalize");
|
|
auto* env_func = find_function(app_image, "omnitrace_set_env");
|
|
auto* mpi_func = find_function(app_image, "omnitrace_set_mpi");
|
|
auto* entr_trace = find_function(app_image, "omnitrace_push_trace");
|
|
auto* exit_trace = find_function(app_image, "omnitrace_pop_trace");
|
|
auto* reg_src_func = find_function(app_image, "omnitrace_register_source");
|
|
auto* reg_cov_func = find_function(app_image, "omnitrace_register_coverage");
|
|
|
|
if(!main_func && main_fname == "main") main_func = find_function(app_image, "_main");
|
|
|
|
//----------------------------------------------------------------------------------//
|
|
//
|
|
// Handle supplemental instrumentation library functions
|
|
//
|
|
//----------------------------------------------------------------------------------//
|
|
|
|
auto add_instr_library = [&](const string_t& _name, const string_t& _beg,
|
|
const string_t& _end) {
|
|
verbprintf(3,
|
|
"Attempting to find instrumentation for '%s' via '%s' and '%s'...\n",
|
|
_name.c_str(), _beg.c_str(), _end.c_str());
|
|
if(_beg.empty() || _end.empty()) return false;
|
|
auto* _beg_func = find_function(app_image, _beg);
|
|
auto* _end_func = find_function(app_image, _end);
|
|
if(_beg_func && _end_func)
|
|
{
|
|
use_stubs[_name] = true;
|
|
beg_stubs[_name] = _beg_func;
|
|
end_stubs[_name] = _end_func;
|
|
used_stub_names.insert(_beg);
|
|
used_stub_names.insert(_end);
|
|
verbprintf(0, "Instrumenting '%s' via '%s' and '%s'...\n", _name.c_str(),
|
|
_beg.c_str(), _end.c_str());
|
|
return true;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
if(!extra_libs.empty())
|
|
{
|
|
verbprintf(2, "Adding extra libraries...\n");
|
|
}
|
|
|
|
for(const auto& itr : extra_libs)
|
|
{
|
|
string_t _name = itr;
|
|
size_t _pos = _name.find_last_of('/');
|
|
if(_pos != npos_v) _name = _name.substr(_pos + 1);
|
|
_pos = _name.find('.');
|
|
if(_pos != npos_v) _name = _name.substr(0, _pos);
|
|
_pos = _name.find("libomnitrace-");
|
|
if(_pos != npos_v)
|
|
_name = _name.erase(_pos, std::string("libomnitrace-").length());
|
|
_pos = _name.find("lib");
|
|
if(_pos == 0) _name = _name.substr(_pos + std::string("lib").length());
|
|
while((_pos = _name.find('-')) != npos_v)
|
|
_name.replace(_pos, 1, "_");
|
|
|
|
verbprintf(2,
|
|
"Supplemental instrumentation library '%s' is named '%s' after "
|
|
"removing everything before last '/', everything after first '.', and "
|
|
"'libomnitrace-'...\n",
|
|
itr.c_str(), _name.c_str());
|
|
|
|
use_stubs[_name] = false;
|
|
|
|
string_t best_init_name = {};
|
|
for(const auto& sitr : init_stub_names)
|
|
{
|
|
if(sitr.find(_name) != npos_v && used_stub_names.count(sitr) == 0)
|
|
{
|
|
verbprintf(
|
|
3, "Found possible match for '%s' instrumentation init: '%s'...\n",
|
|
_name.c_str(), sitr.c_str());
|
|
best_init_name = sitr;
|
|
break;
|
|
}
|
|
}
|
|
|
|
string_t base_fini_name = {};
|
|
for(const auto& sitr : fini_stub_names)
|
|
{
|
|
if(sitr.find(_name) != npos_v && used_stub_names.count(sitr) == 0)
|
|
{
|
|
verbprintf(
|
|
3, "Found possible match for '%s' instrumentation fini: '%s'...\n",
|
|
_name.c_str(), sitr.c_str());
|
|
base_fini_name = sitr;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(add_instr_library(_name, best_init_name, base_fini_name)) continue;
|
|
|
|
// check user-specified signatures first
|
|
for(const auto& bitr : init_stub_names)
|
|
{
|
|
if(used_stub_names.find(bitr) != used_stub_names.end()) continue;
|
|
for(const auto& fitr : fini_stub_names)
|
|
{
|
|
if(used_stub_names.find(fitr) != used_stub_names.end()) continue;
|
|
if(add_instr_library(_name, bitr, fitr))
|
|
goto found_instr_functions; // exit loop after match
|
|
}
|
|
}
|
|
|
|
// check standard function signature if no user-specified matches
|
|
if(add_instr_library(_name, TIMEMORY_JOIN("", "omnitrace_register_" + _name),
|
|
TIMEMORY_JOIN("", "omnitrace_deregister_" + _name)))
|
|
continue;
|
|
|
|
found_instr_functions:
|
|
continue;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------//
|
|
//
|
|
// Check for any issues finding the required functions
|
|
//
|
|
//----------------------------------------------------------------------------------//
|
|
|
|
using pair_t = std::pair<procedure_t*, string_t>;
|
|
|
|
for(const auto& itr :
|
|
{ pair_t(entr_trace, "omnitrace_push_trace"),
|
|
pair_t(exit_trace, "omnitrace_pop_trace"), pair_t(init_func, "omnitrace_init"),
|
|
pair_t(fini_func, "omnitrace_finalize"), pair_t(env_func, "omnitrace_set_env"),
|
|
pair_t(reg_src_func, "omnitrace_register_source"),
|
|
pair_t(reg_cov_func, "omnitrace_register_coverage") })
|
|
{
|
|
if(!itr.first)
|
|
{
|
|
errprintf(-1, "could not find required function :: '%s'\n",
|
|
itr.second.c_str());
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------//
|
|
//
|
|
// Find the entry/exit point of either the main (if executable) or the _init
|
|
// and _fini functions of the library
|
|
//
|
|
//----------------------------------------------------------------------------------//
|
|
|
|
std::vector<point_t*>* main_entr_points = nullptr;
|
|
std::vector<point_t*>* main_exit_points = nullptr;
|
|
|
|
if(main_func)
|
|
{
|
|
verbprintf(2, "Finding main function entry/exit... ");
|
|
main_entr_points = main_func->findPoint(BPatch_entry);
|
|
main_exit_points = main_func->findPoint(BPatch_exit);
|
|
verbprintf(2, "Done\n");
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------//
|
|
//
|
|
// Create the call arguments for the initialization and finalization routines
|
|
// and the snippets which are inserted using those arguments
|
|
//
|
|
//----------------------------------------------------------------------------------//
|
|
|
|
// begin insertion
|
|
if(binary_rewrite)
|
|
{
|
|
verbprintf(2, "Beginning insertion set...\n");
|
|
addr_space->beginInsertionSet();
|
|
}
|
|
|
|
function_signature main_sign("int", "main", "", { "int", "char**" });
|
|
if(main_func)
|
|
{
|
|
verbprintf(2, "Getting main function signature...\n");
|
|
main_sign = get_func_file_line_info(main_func->getModule(), main_func);
|
|
if(main_sign.m_params == "()") main_sign.m_params = "(int argc, char** argv)";
|
|
}
|
|
|
|
verbprintf(2, "Getting call expressions... ");
|
|
|
|
auto _init_arg0 = main_fname;
|
|
if(main_func) main_sign.get();
|
|
|
|
auto main_call_args = omnitrace_call_expr(main_sign.get());
|
|
auto init_call_args = omnitrace_call_expr(instr_mode, binary_rewrite, _init_arg0);
|
|
auto fini_call_args = omnitrace_call_expr();
|
|
auto umpi_call_args = omnitrace_call_expr(use_mpi, is_attached);
|
|
auto none_call_args = omnitrace_call_expr();
|
|
|
|
verbprintf(2, "Done\n");
|
|
verbprintf(2, "Getting call snippets... ");
|
|
|
|
auto init_call = init_call_args.get(init_func);
|
|
auto fini_call = fini_call_args.get(fini_func);
|
|
auto umpi_call = umpi_call_args.get(mpi_func);
|
|
|
|
auto main_beg_call = main_call_args.get(entr_trace);
|
|
|
|
verbprintf(2, "Done\n");
|
|
|
|
for(const auto& itr : use_stubs)
|
|
{
|
|
if(beg_stubs[itr.first] && end_stubs[itr.first])
|
|
{
|
|
beg_expr[itr.first] = none_call_args.get(beg_stubs[itr.first]);
|
|
end_expr[itr.first] = none_call_args.get(end_stubs[itr.first]);
|
|
}
|
|
}
|
|
|
|
std::string _libname = {};
|
|
for(auto&& itr : sharedlibname)
|
|
{
|
|
if(_libname.empty()) _libname = get_absolute_lib_filepath(itr);
|
|
}
|
|
for(auto&& itr : staticlibname)
|
|
{
|
|
if(_libname.empty()) _libname = get_absolute_lib_filepath(itr);
|
|
}
|
|
if(_libname.empty()) _libname = "libomnitrace-dl.so";
|
|
|
|
// prioritize the user environment arguments
|
|
auto env_vars = parser.get<strvec_t>("env");
|
|
env_vars.reserve(env_vars.size() + env_config_variables.size());
|
|
for(auto&& itr : env_config_variables)
|
|
env_vars.emplace_back(itr);
|
|
env_vars.emplace_back(TIMEMORY_JOIN('=', "OMNITRACE_MODE", instr_mode));
|
|
env_vars.emplace_back(TIMEMORY_JOIN('=', "OMNITRACE_MPI_INIT", "OFF"));
|
|
env_vars.emplace_back(TIMEMORY_JOIN('=', "OMNITRACE_MPI_FINALIZE", "OFF"));
|
|
env_vars.emplace_back(
|
|
TIMEMORY_JOIN('=', "OMNITRACE_INIT_ENABLED",
|
|
(user_start_func && user_stop_func) ? "OFF" : "ON"));
|
|
env_vars.emplace_back(
|
|
TIMEMORY_JOIN('=', "OMNITRACE_TIMEMORY_COMPONENTS", default_components));
|
|
env_vars.emplace_back(TIMEMORY_JOIN('=', "OMNITRACE_USE_MPIP",
|
|
(binary_rewrite && use_mpi) ? "ON" : "OFF"));
|
|
env_vars.emplace_back(TIMEMORY_JOIN('=', "OMNITRACE_USE_CODE_COVERAGE",
|
|
(coverage_mode != CODECOV_NONE) ? "ON" : "OFF"));
|
|
if(use_mpi) env_vars.emplace_back(TIMEMORY_JOIN('=', "OMNITRACE_USE_PID", "ON"));
|
|
|
|
for(auto& itr : env_vars)
|
|
{
|
|
auto p = tim::delimit(itr, "=");
|
|
if(p.size() != 2)
|
|
{
|
|
errprintf(0, "environment variable %s not in form VARIABLE=VALUE\n",
|
|
itr.c_str());
|
|
}
|
|
tim::set_env(p.at(0), p.at(1));
|
|
auto _expr = omnitrace_call_expr(p.at(0), p.at(1));
|
|
env_variables.emplace_back(_expr.get(env_func));
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------//
|
|
//
|
|
// Configure the initialization and finalization routines
|
|
//
|
|
//----------------------------------------------------------------------------------//
|
|
|
|
for(const auto& itr : env_variables)
|
|
{
|
|
if(itr) init_names.emplace_back(itr.get());
|
|
}
|
|
|
|
for(const auto& itr : beg_expr)
|
|
{
|
|
if(itr.second)
|
|
{
|
|
verbprintf(1, "+ Adding %s instrumentation...\n", itr.first.c_str());
|
|
init_names.emplace_back(itr.second.get());
|
|
}
|
|
else
|
|
{
|
|
verbprintf(1, "- Skipping %s instrumentation...\n", itr.first.c_str());
|
|
}
|
|
}
|
|
|
|
if(umpi_call) init_names.emplace_back(umpi_call.get());
|
|
if(init_call) init_names.emplace_back(init_call.get());
|
|
if(main_func && main_beg_call) init_names.emplace_back(main_beg_call.get());
|
|
|
|
for(const auto& itr : end_expr)
|
|
if(itr.second) fini_names.emplace_back(itr.second.get());
|
|
if(fini_call) fini_names.emplace_back(fini_call.get());
|
|
|
|
//----------------------------------------------------------------------------------//
|
|
//
|
|
// Sort the available module functions into appropriate containers
|
|
//
|
|
//----------------------------------------------------------------------------------//
|
|
|
|
if(instr_mode != "sampling")
|
|
{
|
|
for(const auto& itr : available_module_functions)
|
|
{
|
|
if(itr.should_instrument())
|
|
{
|
|
_insert_module_function(instrumented_module_functions, itr);
|
|
}
|
|
else
|
|
{
|
|
_insert_module_function(excluded_module_functions, itr);
|
|
}
|
|
if(coverage_mode != CODECOV_NONE)
|
|
{
|
|
if(itr.should_coverage_instrument())
|
|
_insert_module_function(coverage_module_functions, itr);
|
|
}
|
|
if(itr.is_overlapping())
|
|
_insert_module_function(overlapping_module_functions, itr);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// in sampling mode, we instrument either main or add init and fini callbacks
|
|
if(main_func)
|
|
_insert_module_function(instrumented_module_functions,
|
|
module_function{ main_func->getModule(), main_func });
|
|
|
|
for(const auto& itr : available_module_functions)
|
|
{
|
|
_insert_module_function(excluded_module_functions, itr);
|
|
if(coverage_mode != CODECOV_NONE)
|
|
{
|
|
if(itr.should_coverage_instrument())
|
|
_insert_module_function(coverage_module_functions, itr);
|
|
}
|
|
if(itr.is_overlapping())
|
|
_insert_module_function(overlapping_module_functions, itr);
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------//
|
|
//
|
|
// Insert the initialization and finalization routines into the main entry and
|
|
// exit points
|
|
//
|
|
//----------------------------------------------------------------------------------//
|
|
|
|
auto _objs = std::vector<object_t*>{};
|
|
addr_space->getImage()->getObjects(_objs);
|
|
auto _init_sequence = sequence_t{ init_names };
|
|
auto _fini_sequence = sequence_t{ fini_names };
|
|
|
|
if(app_thread && is_attached)
|
|
{
|
|
assert(app_thread != nullptr);
|
|
verbprintf(1, "Executing initial snippets...\n");
|
|
for(auto* itr : init_names)
|
|
app_thread->oneTimeCode(*itr);
|
|
}
|
|
else
|
|
{
|
|
if(main_entr_points)
|
|
{
|
|
verbprintf(1, "Adding main entry snippets...\n");
|
|
addr_space->insertSnippet(_init_sequence, *main_entr_points);
|
|
// insert_instr(addr_space, *main_entr_points, _init_sequence, BPatch_entry);
|
|
}
|
|
else
|
|
{
|
|
for(auto* itr : _objs)
|
|
{
|
|
try
|
|
{
|
|
itr->insertInitCallback(_init_sequence);
|
|
} catch(std::runtime_error& _e)
|
|
{
|
|
errprintf(0, "Dyninst error inserting init callback: %s\n",
|
|
_e.what());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(main_exit_points)
|
|
{
|
|
verbprintf(1, "Adding main exit snippets...\n");
|
|
// insert_instr(addr_space, *main_exit_points, _fini_sequence, BPatch_exit);
|
|
addr_space->insertSnippet(_fini_sequence, *main_exit_points);
|
|
}
|
|
else
|
|
{
|
|
for(auto* itr : _objs)
|
|
{
|
|
try
|
|
{
|
|
itr->insertFiniCallback(_fini_sequence);
|
|
} catch(std::runtime_error& _e)
|
|
{
|
|
errprintf(0, "Dyninst error inserting fini callback: %s\n", _e.what());
|
|
}
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------//
|
|
//
|
|
// Actually insert the instrumentation into the procedures
|
|
//
|
|
//----------------------------------------------------------------------------------//
|
|
if(app_thread)
|
|
{
|
|
verbprintf(2, "Beginning insertion set...\n");
|
|
addr_space->beginInsertionSet();
|
|
}
|
|
|
|
verbprintf(2, "Beginning instrumentation loop...\n");
|
|
auto _report_info = [](int _lvl, const string_t& _action, const string_t& _type,
|
|
const string_t& _reason, const string_t& _name,
|
|
const std::string& _extra = {}) {
|
|
static std::map<std::string, strset_t> already_reported{};
|
|
auto _key = TIMEMORY_JOIN('_', _type, _action, _reason, _name, _extra);
|
|
if(already_reported[_key].count(_name) == 0)
|
|
{
|
|
verbprintf(_lvl, "[%s][%s] %s :: '%s'", _type.c_str(), _action.c_str(),
|
|
_reason.c_str(), _name.c_str());
|
|
if(!_extra.empty()) verbprintf_bare(_lvl, " (%s)", _extra.c_str());
|
|
verbprintf_bare(_lvl, "...\n");
|
|
already_reported[_key].insert(_name);
|
|
}
|
|
};
|
|
|
|
if(instr_mode != "coverage")
|
|
{
|
|
auto _pass_info = std::map<std::string, std::pair<size_t, size_t>>{};
|
|
const int _pass_verbose_lvl = 0;
|
|
for(const auto& itr : instrumented_module_functions)
|
|
{
|
|
if(itr.function == main_func) continue;
|
|
auto _count = itr(addr_space, entr_trace, exit_trace);
|
|
_pass_info[itr.module_name].first += _count.first;
|
|
_pass_info[itr.module_name].second += _count.second;
|
|
|
|
for(const auto& mitr : itr.messages)
|
|
_report_info(std::get<0>(mitr), std::get<1>(mitr), std::get<2>(mitr),
|
|
std::get<3>(mitr), std::get<4>(mitr));
|
|
}
|
|
|
|
// report the trace instrumented functions
|
|
for(auto& itr : _pass_info)
|
|
{
|
|
auto _valid = (verbose_level >= _pass_verbose_lvl ||
|
|
(itr.second.first + itr.second.second) > 0);
|
|
if(!_valid) continue;
|
|
verbprintf(_pass_verbose_lvl, "%4zu instrumented funcs in %s\n",
|
|
itr.second.first, itr.first.c_str());
|
|
_valid = (loop_level_instr &&
|
|
(verbose_level >= _pass_verbose_lvl || itr.second.second > 0));
|
|
if(_valid)
|
|
{
|
|
verbprintf(_pass_verbose_lvl, "%4zu instrumented loops in %s\n",
|
|
itr.second.second, itr.first.c_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
if(coverage_mode != CODECOV_NONE)
|
|
{
|
|
std::map<std::string, std::pair<size_t, size_t>> _covr_info = {};
|
|
const int _covr_verbose_lvl = 1;
|
|
for(const auto& itr : coverage_module_functions)
|
|
{
|
|
if(itr.function == main_func) continue;
|
|
itr.register_source(addr_space, reg_src_func, *main_entr_points);
|
|
auto _count = itr.register_coverage(addr_space, reg_cov_func);
|
|
_covr_info[itr.module_name].first += _count.first;
|
|
_covr_info[itr.module_name].second += _count.second;
|
|
|
|
for(const auto& mitr : itr.messages)
|
|
_report_info(std::get<0>(mitr), std::get<1>(mitr), std::get<2>(mitr),
|
|
std::get<3>(mitr), std::get<4>(mitr));
|
|
}
|
|
|
|
// report the coverage instrumented functions
|
|
for(auto& itr : _covr_info)
|
|
{
|
|
auto _valid = (verbose_level > _covr_verbose_lvl ||
|
|
(itr.second.first + itr.second.second) > 0);
|
|
if(!_valid) continue;
|
|
switch(coverage_mode)
|
|
{
|
|
case CODECOV_NONE:
|
|
{
|
|
break;
|
|
}
|
|
case CODECOV_FUNCTION:
|
|
{
|
|
verbprintf(_covr_verbose_lvl, "%4zu coverage functions in %s\n",
|
|
itr.second.first, itr.first.c_str());
|
|
break;
|
|
}
|
|
case CODECOV_BASIC_BLOCK:
|
|
{
|
|
verbprintf(_covr_verbose_lvl, "%4zu coverage basic blocks in %s\n",
|
|
itr.second.second, itr.first.c_str());
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
verbprintf(1, "\n");
|
|
|
|
if(app_thread)
|
|
{
|
|
verbprintf(2, "Finalizing insertion set...\n");
|
|
bool modified = true;
|
|
bool success = addr_space->finalizeInsertionSet(true, &modified);
|
|
if(!success)
|
|
{
|
|
verbprintf(
|
|
1,
|
|
"Using insertion set failed. Restarting with individual insertion...\n");
|
|
auto _execute_batch = [&addr_space, &entr_trace, &exit_trace](size_t _beg,
|
|
size_t _end) {
|
|
verbprintf(1, "Instrumenting batch of functions [%lu, %lu)\n",
|
|
(unsigned long) _beg, (unsigned long) _end);
|
|
addr_space->beginInsertionSet();
|
|
auto itr = instrumented_module_functions.begin();
|
|
std::advance(itr, _beg);
|
|
for(size_t i = _beg; i < _end; ++i, ++itr)
|
|
(*itr)(addr_space, entr_trace, exit_trace);
|
|
bool _modified = true;
|
|
bool _success = addr_space->finalizeInsertionSet(true, &_modified);
|
|
return _success;
|
|
};
|
|
|
|
auto execute_batch = [&_execute_batch, &addr_space, &entr_trace,
|
|
&exit_trace](size_t _beg) {
|
|
if(!_execute_batch(_beg, _beg + batch_size))
|
|
{
|
|
verbprintf(1,
|
|
"Batch instrumentation of functions [%lu, %lu) failed. "
|
|
"Beginning non-batched instrumentation for this set\n",
|
|
(unsigned long) _beg, (unsigned long) _beg + batch_size);
|
|
auto itr = instrumented_module_functions.begin();
|
|
auto _end = instrumented_module_functions.end();
|
|
std::advance(itr, _beg);
|
|
for(size_t i = _beg; i < _beg + batch_size && itr != _end; ++i, ++itr)
|
|
{
|
|
(*itr)(addr_space, entr_trace, exit_trace);
|
|
}
|
|
}
|
|
return _beg + batch_size;
|
|
};
|
|
|
|
size_t nidx = 0;
|
|
while(nidx < instrumented_module_functions.size())
|
|
{
|
|
nidx = execute_batch(nidx);
|
|
}
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------//
|
|
//
|
|
// Dump the available instrumented modules/functions (re-dump available)
|
|
//
|
|
//----------------------------------------------------------------------------------//
|
|
|
|
dump_info("available", available_module_functions, 0, werror,
|
|
"available_module_functions", print_formats);
|
|
dump_info("instrumented", instrumented_module_functions, 0, werror,
|
|
"instrumented_module_functions", print_formats);
|
|
dump_info("excluded", excluded_module_functions, 0, werror,
|
|
"excluded_module_functions", print_formats);
|
|
if(coverage_mode != CODECOV_NONE)
|
|
dump_info("coverage", coverage_module_functions, 0, werror,
|
|
"coverage_module_functions", print_formats);
|
|
dump_info("overlapping", overlapping_module_functions, 0, werror,
|
|
"overlapping_module_functions", print_formats);
|
|
|
|
auto _dump_info = [](const std::string& _label, const string_t& _mode,
|
|
const fmodset_t& _modset) {
|
|
std::map<std::string, std::vector<std::string>> _data{};
|
|
std::unordered_map<std::string, std::unordered_set<std::string>> _dups{};
|
|
auto _insert = [&](const std::string& _m, const std::string& _v) {
|
|
if(_dups[_m].find(_v) == _dups[_m].end())
|
|
{
|
|
_dups[_m].emplace(_v);
|
|
_data[_m].emplace_back(_v);
|
|
}
|
|
};
|
|
if(_mode == "modules")
|
|
{
|
|
for(const auto& itr : _modset)
|
|
_insert(itr.module_name, TIMEMORY_JOIN("", "[", itr.module_name, "]"));
|
|
}
|
|
else if(_mode == "functions")
|
|
{
|
|
for(const auto& itr : _modset)
|
|
_insert(itr.module_name, TIMEMORY_JOIN("", "[", itr.function_name, "][",
|
|
itr.num_instructions, "]"));
|
|
}
|
|
else if(_mode == "functions+")
|
|
{
|
|
for(const auto& itr : _modset)
|
|
_insert(itr.module_name, TIMEMORY_JOIN("", "[", itr.signature.get(), "][",
|
|
itr.num_instructions, "]"));
|
|
}
|
|
else if(_mode == "pair")
|
|
{
|
|
for(const auto& itr : _modset)
|
|
{
|
|
_insert(itr.module_name, TIMEMORY_JOIN("", "[", itr.module_name,
|
|
"] --> [", itr.function_name, "][",
|
|
itr.num_instructions, "]"));
|
|
}
|
|
}
|
|
else if(_mode == "pair+")
|
|
{
|
|
for(const auto& itr : _modset)
|
|
{
|
|
_insert(itr.module_name, TIMEMORY_JOIN("", "[", itr.module_name,
|
|
"] --> [", itr.signature.get(),
|
|
"][", itr.num_instructions, "]"));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
errprintf(0, "Unknown mode :: %s\n", _mode.c_str());
|
|
}
|
|
for(auto& mitr : _data)
|
|
{
|
|
if(_mode != "modules" && _mode != "pair" && _mode != "pair+")
|
|
std::cout << "\n[" << _label << "] " << mitr.first << ":\n";
|
|
std::sort(mitr.second.begin(), mitr.second.end());
|
|
for(auto& itr : mitr.second)
|
|
{
|
|
std::cout << "[" << _label << "] " << itr << "\n";
|
|
}
|
|
}
|
|
};
|
|
|
|
if(!print_available.empty())
|
|
_dump_info("available", print_available, available_module_functions);
|
|
if(!print_instrumented.empty())
|
|
_dump_info("instrumented", print_instrumented, instrumented_module_functions);
|
|
if(!print_excluded.empty())
|
|
_dump_info("excluded", print_excluded, excluded_module_functions);
|
|
if(!print_coverage.empty())
|
|
_dump_info("coverage", print_coverage, coverage_module_functions);
|
|
if(!print_overlapping.empty())
|
|
_dump_info("overlapping", print_overlapping, overlapping_module_functions);
|
|
|
|
if(simulate) exit(EXIT_SUCCESS);
|
|
|
|
//----------------------------------------------------------------------------------//
|
|
//
|
|
// Either write the instrumented binary or execute the application
|
|
//
|
|
//----------------------------------------------------------------------------------//
|
|
if(binary_rewrite) addr_space->finalizeInsertionSet(false, nullptr);
|
|
|
|
int code = -1;
|
|
if(binary_rewrite)
|
|
{
|
|
const auto& outf = outfile;
|
|
if(outf.find('/') != string_t::npos)
|
|
{
|
|
auto outdir = outf.substr(0, outf.find_last_of('/'));
|
|
tim::makedir(outdir);
|
|
}
|
|
|
|
bool success = app_binary->writeFile(outfile.c_str());
|
|
code = (success) ? EXIT_SUCCESS : EXIT_FAILURE;
|
|
if(success)
|
|
{
|
|
verbprintf(0, "\n");
|
|
if(outfile.find('/') != 0)
|
|
{
|
|
verbprintf(0, "The instrumented executable image is stored in '%s/%s'\n",
|
|
get_cwd().c_str(), outfile.c_str());
|
|
}
|
|
else
|
|
{
|
|
verbprintf(0, "The instrumented executable image is stored in '%s'\n",
|
|
outfile.c_str());
|
|
}
|
|
}
|
|
|
|
if(main_func)
|
|
{
|
|
verbprintf(0, "Getting linked libraries for %s...\n", cmdv0.c_str());
|
|
verbprintf(0, "Consider instrumenting the relevant libraries...\n");
|
|
verbprintf(0, "\n");
|
|
|
|
auto cmdv_envp = std::array<char*, 2>{};
|
|
cmdv_envp.fill(nullptr);
|
|
cmdv_envp.at(0) = strdup("LD_TRACE_LOADED_OBJECTS=1");
|
|
auto ldd = tim::popen::popen(cmdv0.c_str(), nullptr, cmdv_envp.data());
|
|
auto linked_libs = tim::popen::read_ldd_fork(ldd);
|
|
auto perr = tim::popen::pclose(ldd);
|
|
for(auto& itr : cmdv_envp)
|
|
::free(itr);
|
|
|
|
if(perr != 0) perror("Error in omnitrace_fork");
|
|
|
|
for(const auto& itr : linked_libs)
|
|
verbprintf(0, "\t%s\n", itr.c_str());
|
|
|
|
verbprintf(0, "\n");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
verbprintf(0, "Executing...\n");
|
|
|
|
#define WAITPID_DEBUG_MESSAGE(QUERY) \
|
|
{ \
|
|
QUERY; \
|
|
verbprintf(3, \
|
|
"waitpid (%i, %i) returned [%s:%i] :: %s :: code: %i, status %i. " \
|
|
"WIFEXITED(status) = " \
|
|
"%i, WIFSIGNALED(status) = %i\n", \
|
|
cpid, w, __FILE__, __LINE__, #QUERY, code, status, WIFEXITED(status), \
|
|
WIFSIGNALED(status)); \
|
|
}
|
|
|
|
auto _compute_exit_code = [app_thread, &code]() {
|
|
if(app_thread->terminationStatus() == ExitedNormally)
|
|
{
|
|
if(app_thread->isTerminated()) verbprintf(0, "End of omnitrace\n");
|
|
}
|
|
else if(app_thread->terminationStatus() == ExitedViaSignal)
|
|
{
|
|
auto sign = app_thread->getExitSignal();
|
|
fprintf(stderr, "\nApplication exited with signal: %i\n", int(sign));
|
|
}
|
|
code = app_thread->getExitCode();
|
|
};
|
|
|
|
if(!app_thread->isTerminated() && !is_attached)
|
|
{
|
|
pid_t cpid = app_thread->getPid();
|
|
int status = 0;
|
|
app_thread->detach(true);
|
|
do
|
|
{
|
|
status = 0;
|
|
pid_t w = waitpid(cpid, &status, WUNTRACED);
|
|
if(w == -1)
|
|
{
|
|
perror("waitpid");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if(WIFEXITED(status))
|
|
{
|
|
WAITPID_DEBUG_MESSAGE(code = WEXITSTATUS(status));
|
|
}
|
|
else if(WIFSIGNALED(status))
|
|
{
|
|
WAITPID_DEBUG_MESSAGE(code = WTERMSIG(status));
|
|
}
|
|
else if(WIFSTOPPED(status))
|
|
{
|
|
WAITPID_DEBUG_MESSAGE(code = WSTOPSIG(status));
|
|
}
|
|
else if(WIFCONTINUED(status))
|
|
{
|
|
WAITPID_DEBUG_MESSAGE(code = WIFCONTINUED(status));
|
|
}
|
|
} while(WIFEXITED(status) == 0 && WIFSIGNALED(status) == 0);
|
|
}
|
|
else if(!app_thread->isTerminated() && is_attached)
|
|
{
|
|
app_thread->continueExecution();
|
|
while(!app_thread->isTerminated())
|
|
{
|
|
while(bpatch->waitForStatusChange())
|
|
app_thread->continueExecution();
|
|
}
|
|
_compute_exit_code();
|
|
}
|
|
else
|
|
{
|
|
_compute_exit_code();
|
|
}
|
|
}
|
|
|
|
// cleanup
|
|
for(int i = 0; i < argc; ++i)
|
|
free(_argv[i]);
|
|
delete[] _argv;
|
|
for(int i = 0; i < _cmdc; ++i)
|
|
free(_cmdv[i]);
|
|
delete[] _cmdv;
|
|
|
|
verbprintf(0, "End of omnitrace\n");
|
|
verbprintf(1, "Exit code: %i\n", code);
|
|
|
|
if(log_ofs)
|
|
{
|
|
verbprintf(2, "Closing log file...\n");
|
|
log_ofs->close();
|
|
}
|
|
return code;
|
|
}
|
|
|
|
//======================================================================================//
|
|
// query_instr -- check whether there are one or more instrumentation points
|
|
//
|
|
bool
|
|
query_instr(procedure_t* funcToInstr, procedure_loc_t traceLoc, flow_graph_t* cfGraph,
|
|
basic_loop_t* loopToInstrument, bool allow_traps)
|
|
{
|
|
module_t* module = funcToInstr->getModule();
|
|
if(!module) return false;
|
|
|
|
std::vector<point_t*>* _points = nullptr;
|
|
|
|
if(cfGraph && loopToInstrument)
|
|
{
|
|
if(traceLoc == BPatch_entry)
|
|
_points = cfGraph->findLoopInstPoints(BPatch_locLoopEntry, loopToInstrument);
|
|
else if(traceLoc == BPatch_exit)
|
|
_points = cfGraph->findLoopInstPoints(BPatch_locLoopExit, loopToInstrument);
|
|
}
|
|
else
|
|
{
|
|
_points = funcToInstr->findPoint(traceLoc);
|
|
}
|
|
|
|
if(_points == nullptr) return false;
|
|
if(_points->empty()) return false;
|
|
|
|
size_t _n = _points->size();
|
|
for(auto& itr : *_points)
|
|
{
|
|
if(!itr)
|
|
--_n;
|
|
else if(itr && !allow_traps && itr->usesTrap_NP())
|
|
--_n;
|
|
}
|
|
|
|
return (_n > 0);
|
|
}
|
|
|
|
std::tuple<size_t, size_t>
|
|
query_instr(procedure_t* funcToInstr, procedure_loc_t traceLoc, flow_graph_t* cfGraph,
|
|
basic_loop_t* loopToInstrument)
|
|
{
|
|
module_t* module = funcToInstr->getModule();
|
|
if(!module) return { 0, 0 };
|
|
|
|
if(!cfGraph) cfGraph = funcToInstr->getCFG();
|
|
|
|
std::vector<point_t*>* _points = nullptr;
|
|
|
|
if((cfGraph && loopToInstrument) ||
|
|
(traceLoc == BPatch_locLoopEntry || traceLoc == BPatch_locLoopExit))
|
|
{
|
|
if(!cfGraph) throw std::runtime_error("No control flow graph");
|
|
if(!loopToInstrument) throw std::runtime_error("No loop to instrument");
|
|
|
|
if(traceLoc == BPatch_entry || traceLoc == BPatch_locLoopEntry)
|
|
{
|
|
_points = cfGraph->findLoopInstPoints(BPatch_locLoopEntry, loopToInstrument);
|
|
}
|
|
else if(traceLoc == BPatch_exit || traceLoc == BPatch_locLoopExit)
|
|
{
|
|
_points = cfGraph->findLoopInstPoints(BPatch_locLoopExit, loopToInstrument);
|
|
}
|
|
else
|
|
{
|
|
throw std::runtime_error("unsupported trace location :: " +
|
|
std::to_string(traceLoc));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_points = funcToInstr->findPoint(traceLoc);
|
|
}
|
|
|
|
if(_points == nullptr) return { 0, 0 };
|
|
if(_points->empty()) return { 0, 0 };
|
|
|
|
size_t _n = 0;
|
|
size_t _t = 0;
|
|
for(auto& itr : *_points)
|
|
{
|
|
if(itr)
|
|
{
|
|
++_n;
|
|
if(itr->usesTrap_NP()) ++_t;
|
|
}
|
|
}
|
|
|
|
return { _n, _t };
|
|
}
|
|
|
|
namespace
|
|
{
|
|
//======================================================================================//
|
|
//
|
|
std::string
|
|
canonicalize(std::string _path)
|
|
{
|
|
if(_path.find("./") == 0)
|
|
_path = _path.replace(0, 1, get_cwd());
|
|
else if(_path.find("../") == 0)
|
|
_path = _path.insert(0, get_cwd() + "/");
|
|
|
|
auto _leading_dash = (_path.find('/') == 0);
|
|
auto _pieces = tim::delimit(_path, "/");
|
|
std::reverse(_pieces.begin(), _pieces.end());
|
|
auto _tree = std::vector<std::string>{};
|
|
for(size_t i = 0; i < _pieces.size(); ++i)
|
|
{
|
|
const auto& itr = _pieces.at(i);
|
|
if(itr == ".")
|
|
{
|
|
continue;
|
|
}
|
|
else if(itr == "..")
|
|
++i;
|
|
else
|
|
_tree.emplace_back(itr);
|
|
}
|
|
std::reverse(_tree.begin(), _tree.end());
|
|
auto _cpath = std::string{ (_leading_dash) ? "/" : "" };
|
|
for(size_t i = 0; i < _tree.size() - 1; ++i)
|
|
_cpath += _tree.at(i) + "/";
|
|
_cpath += _tree.back();
|
|
return _cpath;
|
|
}
|
|
|
|
//======================================================================================//
|
|
//
|
|
std::string
|
|
absolute(std::string _path)
|
|
{
|
|
if(_path.find('/') == 0) return canonicalize(_path);
|
|
return canonicalize(JOIN('/', get_cwd(), _path));
|
|
}
|
|
|
|
//======================================================================================//
|
|
//
|
|
std::string
|
|
get_absolute_filepath(std::string _name, const strvec_t& _search_paths)
|
|
{
|
|
if(!_name.empty() && (!exists(_name) || !is_file(_name)))
|
|
{
|
|
auto _orig = _name;
|
|
for(auto itr : _search_paths)
|
|
{
|
|
if(!is_directory(itr) || is_file(itr)) itr = filepath::dirname(itr);
|
|
|
|
auto _exists = false;
|
|
OMNITRACE_ADD_LOG_ENTRY("searching", itr, "for", _name);
|
|
for(const auto& pitr :
|
|
{ absolute(JOIN('/', itr, _name)),
|
|
absolute(JOIN('/', itr, filepath::basename(_name))) })
|
|
{
|
|
_exists = exists(pitr) && is_file(pitr);
|
|
if(_exists)
|
|
{
|
|
_name = pitr;
|
|
verbprintf(1, "Resolved '%s' to '%s'...\n", _orig.c_str(),
|
|
_name.c_str());
|
|
break;
|
|
}
|
|
}
|
|
if(_exists) break;
|
|
}
|
|
|
|
if(!exists(_name))
|
|
{
|
|
using array_config_t = timemory::join::array_config;
|
|
auto _search_paths_v =
|
|
timemory::join::join(array_config_t{ ", ", "", "" }, bin_search_paths);
|
|
verbprintf(
|
|
0, "Warning! File path to '%s' could not be determined... search: %s\n",
|
|
_name.c_str(), _search_paths_v.c_str());
|
|
}
|
|
}
|
|
else if(!_name.empty())
|
|
{
|
|
auto _orig = _name;
|
|
_name = absolute(_name);
|
|
verbprintf(1, "Resolved '%s' to '%s'...\n", _orig.c_str(), _name.c_str());
|
|
}
|
|
|
|
return _name;
|
|
}
|
|
|
|
//======================================================================================//
|
|
//
|
|
std::string
|
|
get_absolute_filepath(std::string _name)
|
|
{
|
|
auto _search_paths = strvec_t{};
|
|
auto _combine_paths = std::vector<strvec_t>{ bin_search_paths, lib_search_paths };
|
|
auto _base_name = std::string_view{ filepath::basename(_name) };
|
|
// if the name looks like a library, put the lib_search_paths first
|
|
if(_base_name.find("lib") == 0 || _base_name.find(".so") != std::string::npos ||
|
|
_base_name.find(".a") != std::string::npos)
|
|
std::reverse(_combine_paths.begin(), _combine_paths.end());
|
|
_search_paths.reserve(bin_search_paths.size() + lib_search_paths.size());
|
|
for(const auto& pitr : _combine_paths)
|
|
for(const auto& itr : pitr)
|
|
_search_paths.emplace_back(itr);
|
|
|
|
return get_absolute_filepath(std::move(_name), _search_paths);
|
|
}
|
|
|
|
//======================================================================================//
|
|
//
|
|
std::string
|
|
get_absolute_exe_filepath(std::string exe_name)
|
|
{
|
|
return get_absolute_filepath(std::move(exe_name), bin_search_paths);
|
|
}
|
|
|
|
//======================================================================================//
|
|
//
|
|
std::string
|
|
get_absolute_lib_filepath(std::string lib_name)
|
|
{
|
|
auto _orig_name = lib_name;
|
|
lib_name = get_absolute_filepath(std::move(lib_name), lib_search_paths);
|
|
if(_orig_name == lib_name && !exists(lib_name) &&
|
|
lib_name.find(".so") == std::string::npos &&
|
|
lib_name.find(".a") == std::string::npos)
|
|
{
|
|
lib_name = get_absolute_filepath(lib_name + ".so", lib_search_paths);
|
|
}
|
|
return lib_name;
|
|
}
|
|
|
|
bool
|
|
exists(const std::string& name)
|
|
{
|
|
return filepath::exists(absolute(name));
|
|
}
|
|
|
|
bool
|
|
is_file(std::string _name)
|
|
{
|
|
_name = get_realpath(_name);
|
|
struct stat buffer;
|
|
return (stat(_name.c_str(), &buffer) == 0 && S_ISREG(buffer.st_mode) != 0);
|
|
}
|
|
|
|
bool
|
|
is_directory(std::string _name)
|
|
{
|
|
_name = get_realpath(_name);
|
|
struct stat buffer;
|
|
return (stat(_name.c_str(), &buffer) == 0 && S_ISDIR(buffer.st_mode) != 0);
|
|
}
|
|
|
|
std::string
|
|
get_realpath(const std::string& _f)
|
|
{
|
|
return filepath::realpath(_f, nullptr, false);
|
|
}
|
|
|
|
std::string
|
|
get_cwd()
|
|
{
|
|
#if defined(__GNUC__)
|
|
auto* _cwd_c = getcwd(nullptr, 0);
|
|
auto _cwd_v = std::string{ _cwd_c };
|
|
free(_cwd_c);
|
|
return _cwd_v;
|
|
#else
|
|
char cwd[PATH_MAX];
|
|
auto* _cwd_v = getcwd(cwd, PATH_MAX);
|
|
return (_cwd_v) ? std::string{ _cwd_v } : get_env<std::string>("PWD");
|
|
#endif
|
|
}
|
|
|
|
using tim::dirname;
|
|
|
|
void
|
|
find_dyn_api_rt()
|
|
{
|
|
#if defined(OMNITRACE_BUILD_DYNINST)
|
|
std::string _dyn_api_rt_base =
|
|
(binary_rewrite) ? "libomnitrace-rt" : "libdyninstAPI_RT";
|
|
#else
|
|
std::string _dyn_api_rt_base = "libdyninstAPI_RT";
|
|
#endif
|
|
|
|
auto _dyn_api_rt_env =
|
|
tim::get_env<std::string>("DYNINSTAPI_RT_LIB", _dyn_api_rt_base + ".so");
|
|
auto _dyn_api_rt_abs = get_absolute_lib_filepath(_dyn_api_rt_env);
|
|
|
|
if(!exists(_dyn_api_rt_abs))
|
|
_dyn_api_rt_abs = get_absolute_lib_filepath(_dyn_api_rt_base + ".a");
|
|
|
|
if(exists(_dyn_api_rt_abs))
|
|
{
|
|
namespace join = ::timemory::join;
|
|
tim::set_env<string_t>("DYNINSTAPI_RT_LIB", _dyn_api_rt_abs, 1);
|
|
tim::set_env<string_t>("DYNINST_REWRITER_PATHS",
|
|
join::join(join::array_config{ ":", "", "" },
|
|
dirname(_dyn_api_rt_abs), lib_search_paths),
|
|
1);
|
|
}
|
|
|
|
auto _v = tim::get_env<string_t>("DYNINSTAPI_RT_LIB", "");
|
|
verbprintf(0, "DYNINST_API_RT: %s\n", (_v.empty()) ? "<unknown>" : _v.c_str());
|
|
}
|
|
} // namespace
|