// MIT License // // Copyright (c) 2025 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 "interop.hpp" #include "pysqlite_Connection.h" #include "lib/common/defines.hpp" #include "lib/common/logging.hpp" #include "lib/common/static_object.hpp" #include "lib/common/utility.hpp" #include "lib/output/timestamps.hpp" #include #include #include #include #include #include #include #include #include namespace rocpd { namespace interop { namespace { namespace sdk = ::rocprofiler::sdk; namespace common = ::rocprofiler::common; // namespace tool = ::rocprofiler::tool; // namespace sql = ::rocprofiler::tool::sql; using sqlite3_open_func_t = int (*)(const char*, sqlite3**); using sqlite3_open_v2_func_t = int (*)(const char*, sqlite3**, int, const char*); using sqlite3_close_func_t = int (*)(sqlite3*); gotcha_wrappee_handle_t orig_sqlite3_open = {}; gotcha_wrappee_handle_t orig_sqlite3_open_v2 = {}; gotcha_wrappee_handle_t orig_sqlite3_close = {}; gotcha_wrappee_handle_t orig_sqlite3_close_v2 = {}; using sqlite_object_map_t = std::unordered_map; thread_local sqlite3* last_sqlite3 = nullptr; auto sqlite_obj_mapping = sqlite_object_map_t{}; auto* get_mapping() { static auto*& _v = common::static_object::construct(); return _v; } void erase_connection(sqlite3* pDb) { if(!get_mapping()) return; if(last_sqlite3 && last_sqlite3 == pDb) last_sqlite3 = nullptr; auto itr = std::find_if(get_mapping()->begin(), get_mapping()->end(), [pDb](const auto& val) { return (val.second == pDb); }); if(itr != get_mapping()->end()) get_mapping()->erase(itr); } namespace impl { int sqlite3_open(const char* filename, /* Database filename (UTF-8) */ sqlite3** ppDb /* OUT: SQLite db handle */ ) { ROCP_TRACE << fmt::format("invoking {}... {}", __FUNCTION__, filename); auto func = reinterpret_cast(gotcha_get_wrappee(orig_sqlite3_open)); auto ret = func(filename, ppDb); if(ppDb) last_sqlite3 = *ppDb; return ret; } int sqlite3_open_v2(const char* filename, sqlite3** ppDb, int flags, const char* zVfs) { ROCP_TRACE << fmt::format("invoking {}... {}", __FUNCTION__, filename); auto func = reinterpret_cast(gotcha_get_wrappee(orig_sqlite3_open_v2)); auto ret = func(filename, ppDb, flags, zVfs); if(ppDb) last_sqlite3 = *ppDb; return ret; } int sqlite3_close(sqlite3* pDb) { ROCP_TRACE << fmt::format("invoking {}... ", __FUNCTION__); auto func = reinterpret_cast(gotcha_get_wrappee(orig_sqlite3_close)); auto ret = func(pDb); erase_connection(pDb); return ret; } int sqlite3_close_v2(sqlite3* pDb) { ROCP_TRACE << fmt::format("invoking {}... ", __FUNCTION__); auto func = reinterpret_cast(gotcha_get_wrappee(orig_sqlite3_close_v2)); auto ret = func(pDb); erase_connection(pDb); return ret; } } // namespace impl auto bindings = std::array{ gotcha_binding_t{"sqlite3_open", reinterpret_cast(impl::sqlite3_open), &orig_sqlite3_open}, gotcha_binding_t{"sqlite3_close", reinterpret_cast(impl::sqlite3_close), &orig_sqlite3_close}, gotcha_binding_t{"sqlite3_open_v2", reinterpret_cast(impl::sqlite3_open_v2), &orig_sqlite3_open_v2}, gotcha_binding_t{"sqlite3_close_v2", reinterpret_cast(impl::sqlite3_close_v2), &orig_sqlite3_close_v2}, }; } // namespace void activate_gotcha_bindings() { #if defined(GOTCHA_INIT) && GOTCHA_INIT > 0 // initialize gotcha gotcha_init_config_t gotcha_cfg = { .size = sizeof(gotcha_init_config_t), .dl_open_bind = 0, .dl_sym_bind = 0}; gotcha_init(&gotcha_cfg); #endif // this ensures that the sqlite3 module is imported and the sqlite3_open_v2 and sqlite3_close_v2 // in lib-dynload/_sqlite3.cpython-*.so are remapped to the gotcha bindings. // this is needed to ensure that the sqlite3 connections are captured by gotcha { auto sqlite3_mod = py::module_::import("sqlite3"); auto ret = sqlite3_mod.attr("connect")(":memory:"); ret.attr("close")(); } // activate the gotcha wrappers auto _err = gotcha_wrap(bindings.data(), bindings.size(), "rocpd.sqlite3"); ROCP_WARNING_IF(_err != GOTCHA_SUCCESS) << "gotcha error for rocpd.sqlite3"; } sqlite3* map_connection(py::object obj) { if(!get_mapping()) return nullptr; get_mapping()->emplace(obj.ptr(), last_sqlite3); auto* _ret = get_mapping()->at(obj.ptr()); ROCP_INFO << "[pyrocpd][mapping] " << obj.ptr() << " <-> " << _ret; return get_mapping()->at(obj.ptr()); } sqlite3* get_connection(py::object&& obj) { if(!obj) return nullptr; if(get_mapping()) { if(auto itr = get_mapping()->find(obj.ptr()); itr != get_mapping()->end() && itr->second != nullptr) { ROCP_INFO << fmt::format( "sqlite3 python connection ({}) mapped to sqlite3* ({}) safely " "via gotcha capture of sqlite3_open", sdk::utility::as_hex(obj.ptr(), 16), sdk::utility::as_hex(itr->second, 16)); return itr->second; } } ROCP_CI_LOG(WARNING) << fmt::format( "sqlite3 python connection ({}) not captured by gotcha... accessing sqlite3* via " "reinterpret_cast(PyObject*&)->db :: [unsafe]", sdk::utility::as_hex(obj.ptr(), 16)); return reinterpret_cast(obj.ptr())->db; } } // namespace interop } // namespace rocpd