Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add some interface to installer #23

Merged
merged 1 commit into from
Dec 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,15 @@ jobs:
- name: Checkout code
uses: actions/checkout@v2

- name: Set up Python 3.8
uses: actions/setup-python@v2
with:
python-version: 3.8

- name: Set up CMake
run: sudo apt-get install -y cmake
run: |
sudo apt-get install -y cmake
pip install pytest

- name: Build project
run: |
Expand All @@ -26,11 +33,16 @@ jobs:
cd build
cmake ..
make
cd ..
pip wheel .
pip install --force-reinstall *.whl

- name: Run tests
run: |
cd build
ctest -R
cd ../
# pytest test

- name: Upload test results
uses: actions/upload-artifact@v2
Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
build/**
*.whl
.vscode/**
dist/**
dist/**
**/__pycache__/**
6 changes: 4 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ endif()
add_compile_options(-Wall -Wextra -Wno-unused-parameter
-Wno-unused-function -Wno-unused-result -Wno-missing-field-initializers
-Wno-unknown-pragmas -Wno-type-limits -Wno-array-bounds -Wno-unknown-pragmas
-Wno-strict-overflow -Wno-strict-aliasing -Wno-missing-braces -Wno-uninitialized)
-Wno-strict-overflow -Wno-strict-aliasing -Wno-missing-braces -Wno-uninitialized
-Werror=return-type -Werror)


if (NOT SKBUILD)
message(WARNING "\
Expand Down Expand Up @@ -70,7 +72,7 @@ install(TARGETS cuda_mock_impl LIBRARY DESTINATION cuda_mock)
enable_testing()
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/third_party/googletest)

file(GLOB TEST_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/test/*.cpp")
file(GLOB TEST_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/test/**/*.cpp")
add_executable(UnitTest ${TEST_SOURCES})
target_link_libraries(UnitTest gtest gtest_main logger plthook)
add_test(NAME UnitTest COMMAND UnitTest)
Expand Down
21 changes: 19 additions & 2 deletions lib/backtrace.h → include/backtrace.h
Original file line number Diff line number Diff line change
@@ -1,18 +1,35 @@
#pragma once

#include <functional>
#include <iosfwd>
#include <string>
#include <unordered_map>
#include <vector>

namespace trace {

class CallFrames {
public:
static constexpr size_t kMaxStackDeep = 1024;

CallFrames() = default;

bool CollectNative();
bool CollectPython();

friend std::ostream& operator<<(std::ostream& os, const CallFrames& frames);

private:
std::vector<void*> buffers_;
std::vector<std::string> native_frames_;
std::vector<std::string> python_frames_;
};

class BackTraceCollection {
public:
class CallStackInfo {
public:
static constexpr size_t kMaxStackDeep = 256;

static constexpr size_t kMaxStackDeep = 1024;
explicit CallStackInfo(
const std::function<const void*(const std::string&)>& getBaseAddr)
: getBaseAddr_(getBaseAddr) {
Expand Down
88 changes: 78 additions & 10 deletions include/hook.h
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
#pragma once

#include <algorithm>
#include <functional>
#include <iosfwd>
#include <memory>
#include <vector>
#include "logger/logger.h"

namespace hook {
/**
Expand All @@ -16,6 +19,16 @@ struct OriginalInfo {
void* relaPtr = nullptr;
void* oldFuncPtr = nullptr;
void** pltTablePtr = nullptr;
OriginalInfo() = default;
OriginalInfo(const OriginalInfo& info) { *this = info; }
OriginalInfo& operator=(const OriginalInfo& info) {
libName = info.libName;
basePtr = info.basePtr;
relaPtr = info.relaPtr;
oldFuncPtr = info.oldFuncPtr;
pltTablePtr = info.pltTablePtr;
return *this;
}
};

std::ostream& operator<<(std::ostream& os, const OriginalInfo& info);
Expand All @@ -27,19 +40,67 @@ struct HookInstaller {
std::function<void(void)> onSuccess;
};


void install_hook(const HookInstaller& installer);

void uninstall_hook(const OriginalInfo& info);

template <typename DerivedT, typename T = void>
struct MemberDetector : std::false_type {
static bool targetSym(DerivedT* self, const char* name) {
return static_cast<DerivedT*>(self)->targetSym(name);
}
static void* newFuncPtr(DerivedT* self, const hook::OriginalInfo& info) {
return static_cast<DerivedT*>(self)->newFuncPtr(info);
}
};

// TODO(lipracer): mix predefine mode and dynamic mode
template <typename DerivedT>
struct MemberDetector<DerivedT,
std::void_t<decltype(std::declval<DerivedT>().symbols)>>
: std::true_type {
static bool targetSym(DerivedT* self, const char* name) {
for (auto& sym : static_cast<DerivedT*>(self)->symbols) {
if (!strcmp(name, std::get<0>(sym))) {
return true;
}
}
return false;
}
static void* newFuncPtr(DerivedT* self, const hook::OriginalInfo& info) {
auto iter = std::find_if(
std::begin(static_cast<DerivedT*>(self)->symbols),
std::end(static_cast<DerivedT*>(self)->symbols), [self](auto& sym) {
return !strcmp(static_cast<DerivedT*>(self)->curSymName(),
std::get<0>(sym));
});
*std::get<2>(*iter) = info.oldFuncPtr;
return std::get<1>(*iter);
}
};

template <typename DerivedT>
struct PreDefineInterface {
bool targetSym(const char* name) {
return MemberDetector<DerivedT>::targetSym(static_cast<DerivedT*>(this),
name);
}

void* newFuncPtr(const hook::OriginalInfo& info) {
return MemberDetector<DerivedT>::newFuncPtr(
static_cast<DerivedT*>(this), info);
}
};

template <typename DerivedT>
struct HookInstallerWrap
: public std::enable_shared_from_this<HookInstallerWrap<DerivedT>> {
: public std::enable_shared_from_this<HookInstallerWrap<DerivedT>>,
PreDefineInterface<DerivedT> {
// NB: c++ std::shared_ptr and enable_shared_from_this
// shared_from_this can't call in ctor
void install() { install_hook(buildInstaller()); }

~HookInstallerWrap() { uninstall_hook(orgInfo); }
~HookInstallerWrap() = default;

bool targetLib(const char* name) {
libName = name;
Expand All @@ -48,17 +109,22 @@ struct HookInstallerWrap
}
bool targetSym(const char* name) {
symName = name;
isSymbol = static_cast<DerivedT*>(this)->targetSym(name);
return isSymbol;
return static_cast<PreDefineInterface<DerivedT>*>(this)->targetSym(
name);
}

void* newFuncPtr(const hook::OriginalInfo& info) {
orgInfo = info;
return static_cast<DerivedT*>(this)->newFuncPtr(info);
orgInfos.emplace_back(
std::unique_ptr<OriginalInfo, std::function<void(OriginalInfo*)>>(
new OriginalInfo(info), [](OriginalInfo* info) {
uninstall_hook(*info);
delete info;
}));
return static_cast<PreDefineInterface<DerivedT>*>(this)->newFuncPtr(
info);
}

void onSuccess() {
static_cast<DerivedT*>(this)->onSuccess(); }
void onSuccess() { static_cast<DerivedT*>(this)->onSuccess(); }

const char* curSymName() const { return symName; }

Expand All @@ -85,7 +151,9 @@ struct HookInstallerWrap
bool isSymbol{false};
const char* libName{nullptr};
const char* symName{nullptr};
OriginalInfo orgInfo;
std::vector<
std::unique_ptr<OriginalInfo, std::function<void(OriginalInfo*)>>>
orgInfos;
};

} // namespace hook
4 changes: 3 additions & 1 deletion include/logger/logger.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,9 @@ struct LogConfig {
kSync,
kAsync,
};
size_t pageSize{4 * 1024};
// NB: some time log string over 4K
// TODO: need check too long string
size_t pageSize{4 * 1024 * 1024};
LoggerMode mode{kAsync};
std::FILE* stream{stdout};
};
Expand Down
1 change: 1 addition & 0 deletions lib/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ add_library(plthook STATIC
target_compile_definitions(plthook
PRIVATE FMT_HEADER_ONLY
)
target_include_directories(plthook PUBLIC ${Python_INCLUDE_DIRS})

target_link_libraries(plthook PRIVATE pthread dl)

74 changes: 72 additions & 2 deletions lib/backtrace.cpp
Original file line number Diff line number Diff line change
@@ -1,19 +1,89 @@
#include "backtrace.h"

#include <Python.h>
#include <dlfcn.h>
#include <elf.h>
#include <errno.h>
#include <execinfo.h>
#include <frameobject.h>
#include <unistd.h>

#include <algorithm>
#include <fstream>
#include <regex>

#include "logger/logger.h"
#include "support.h"

namespace trace {

bool CallFrames::CollectNative() {
buffers_.clear();
buffers_.resize(kMaxStackDeep, nullptr);
native_frames_.reserve(kMaxStackDeep);
char** symbols = nullptr;
int num = backtrace(buffers_.data(), kMaxStackDeep);
CHECK(num > 0, "Expect frams num {} > 0!", num);
symbols = backtrace_symbols(buffers_.data(), num);
if (symbols == nullptr) {
return false;
}
Dl_info info;
for (int j = 0; j < num; j++) {
if (dladdr(buffers_[j], &info) && info.dli_sname) {
auto demangled = __support__demangle(info.dli_sname);
std::string path(info.dli_fname);
std::stringstream ss;
ss << " frame " << j << path << ":" << demangled;
native_frames_.push_back(ss.str());
} else {
// filtering useless print
// LOG(WARN) << " frame " << j << buffers_[j];
}
}
free(symbols);
return true;
}

bool CallFrames::CollectPython() {
python_frames_.reserve(kMaxStackDeep);
// Acquire the Global Interpreter Lock (GIL) before calling Python C API
// functions from non-Python threads.
PyGILState_STATE gstate = PyGILState_Ensure();
// https://stackoverflow.com/questions/1796510/accessing-a-python-traceback-from-the-c-api
PyThreadState* tstate = PyThreadState_GET();
if (NULL != tstate && NULL != tstate->frame) {
PyFrameObject* frame = tstate->frame;

while (NULL != frame) {
// int line = frame->f_lineno;
/*
frame->f_lineno will not always return the correct line number
you need to call PyCode_Addr2Line().
*/
int line = PyCode_Addr2Line(frame->f_code, frame->f_lasti);
const char* filename = PyUnicode_AsUTF8(frame->f_code->co_filename);
const char* funcname = PyUnicode_AsUTF8(frame->f_code->co_name);
std::stringstream ss;
ss << " " << filename << "(" << line << "): " << funcname;
python_frames_.push_back(ss.str());
frame = frame->f_back;
}
}
PyGILState_Release(gstate);
return !python_frames_.empty();
}

std::ostream& operator<<(std::ostream& os, const CallFrames& frames) {
for (const auto& f : frames.python_frames_) {
os << f << "\n";
}
for (const auto& f : frames.native_frames_) {
os << f << "\n";
}
return os;
}

bool BackTraceCollection::CallStackInfo::snapshot() {
void* buffer[kMaxStackDeep] = {0};
char** symbols = nullptr;
Expand Down Expand Up @@ -171,7 +241,7 @@ bool BackTraceCollection::CallStackInfo::parse() {
// backtrace format lib_name(symbol_name(+add)?) [address]
std::vector<std::string> tmp_backtrace;
tmp_backtrace.reserve(backtrace_.size());
#define push_and_continue() \
#define push_and_continue() \
tmp_backtrace.push_back(line); \
continue;

Expand Down Expand Up @@ -330,7 +400,7 @@ void BackTraceCollection::dump() {
LOG(WARN) << "ignore:[call " << std::get<1>(stack_info) << " times"
<< "]\n";
LOG(WARN) << std::get<0>(stack_info);
if (!std::get<0>(stack_info).parse()) {
if (!std::get<0>(stack_info).parse()) {
LOG(WARN) << "parse fail!";
}
LOG(WARN) << "=========================parsed backtrace "
Expand Down
Loading
Loading