// vim: set sts=2 ts=8 sw=2 tw=99 et: // // Copyright (C) 2006-2015 AlliedModders LLC // // This file is part of SourcePawn. SourcePawn is free software: you can // redistribute it and/or modify it under the terms of the GNU General Public // License as published by the Free Software Foundation, either version 3 of // the License, or (at your option) any later version. // // You should have received a copy of the GNU General Public License along with // SourcePawn. If not, see http://www.gnu.org/licenses/. // #include #include #include #include #include "plugin-runtime.h" #include "x86/jit_x86.h" #include "plugin-context.h" #include "environment.h" #include "md5/md5.h" using namespace sp; using namespace SourcePawn; static inline bool IsPointerCellAligned(void *p) { return uintptr_t(p) % 4 == 0; } PluginRuntime::PluginRuntime(LegacyImage *image) : image_(image), paused_(false), computed_code_hash_(false), computed_data_hash_(false) { code_ = image_->DescribeCode(); data_ = image_->DescribeData(); memset(code_hash_, 0, sizeof(code_hash_)); memset(data_hash_, 0, sizeof(data_hash_)); ke::AutoLock lock(Environment::get()->lock()); Environment::get()->RegisterRuntime(this); } PluginRuntime::~PluginRuntime() { // The watchdog thread takes the global JIT lock while it patches all // runtimes. It is not enough to ensure that the unlinking of the runtime is // protected; we cannot delete functions or code while the watchdog might be // executing. Therefore, the entire destructor is guarded. ke::AutoLock lock(Environment::get()->lock()); Environment::get()->DeregisterRuntime(this); for (uint32_t i = 0; i < image_->NumPublics(); i++) delete entrypoints_[i]; for (size_t i = 0; i < m_JitFunctions.length(); i++) delete m_JitFunctions[i]; } bool PluginRuntime::Initialize() { if (!ke::IsAligned(code_.bytes, sizeof(cell_t))) { // Align the code section. aligned_code_ = new uint8_t[code_.length]; if (!aligned_code_) return false; memcpy(aligned_code_, code_.bytes, code_.length); code_.bytes = aligned_code_; } natives_ = new sp_native_t[image_->NumNatives()]; if (!natives_) return false; memset(natives_, 0, sizeof(sp_native_t) * image_->NumNatives()); publics_ = new sp_public_t[image_->NumPublics()]; if (!publics_) return false; memset(publics_, 0, sizeof(sp_public_t) * image_->NumPublics()); pubvars_ = new sp_pubvar_t[image_->NumPubvars()]; if (!pubvars_) return false; memset(pubvars_, 0, sizeof(sp_pubvar_t) * image_->NumPubvars()); entrypoints_ = new ScriptedInvoker *[image_->NumPublics()]; if (!entrypoints_) return false; memset(entrypoints_, 0, sizeof(ScriptedInvoker *) * image_->NumPublics()); context_ = new PluginContext(this); if (!context_->Initialize()) return false; SetupFloatNativeRemapping(); if (!function_map_.init(32)) return false; return true; } struct NativeMapping { const char *name; unsigned opcode; }; static const NativeMapping sNativeMap[] = { { "FloatAbs", OP_FABS }, { "FloatAdd", OP_FLOATADD }, { "FloatSub", OP_FLOATSUB }, { "FloatMul", OP_FLOATMUL }, { "FloatDiv", OP_FLOATDIV }, { "float", OP_FLOAT }, { "FloatCompare", OP_FLOATCMP }, { "RoundToCeil", OP_RND_TO_CEIL }, { "RoundToZero", OP_RND_TO_ZERO }, { "RoundToFloor", OP_RND_TO_FLOOR }, { "RoundToNearest", OP_RND_TO_NEAREST }, { "__FLOAT_GT__", OP_FLOAT_GT }, { "__FLOAT_GE__", OP_FLOAT_GE }, { "__FLOAT_LT__", OP_FLOAT_LT }, { "__FLOAT_LE__", OP_FLOAT_LE }, { "__FLOAT_EQ__", OP_FLOAT_EQ }, { "__FLOAT_NE__", OP_FLOAT_NE }, { "__FLOAT_NOT__", OP_FLOAT_NOT }, { NULL, 0 }, }; void PluginRuntime::SetupFloatNativeRemapping() { float_table_ = new floattbl_t[image_->NumNatives()]; for (size_t i = 0; i < image_->NumNatives(); i++) { const char *name = image_->GetNative(i); const NativeMapping *iter = sNativeMap; while (iter->name) { if (strcmp(name, iter->name) == 0) { float_table_[i].found = true; float_table_[i].index = iter->opcode; break; } iter++; } } } unsigned PluginRuntime::GetNativeReplacement(size_t index) { if (!float_table_[index].found) return OP_NOP; return float_table_[index].index; } void PluginRuntime::SetName(const char *name) { size_t len = strlen(name); name_ = new char[len + 1]; strcpy(name_, name); } static cell_t InvalidNative(IPluginContext *pCtx, const cell_t *params) { return pCtx->ThrowNativeErrorEx(SP_ERROR_INVALID_NATIVE, "Invalid native"); } void PluginRuntime::AddJittedFunction(CompiledFunction *fn) { m_JitFunctions.append(fn); ucell_t pcode_offset = fn->GetCodeOffset(); FunctionMap::Insert p = function_map_.findForAdd(pcode_offset); assert(!p.found()); function_map_.add(p, pcode_offset, fn); } CompiledFunction * PluginRuntime::GetJittedFunctionByOffset(cell_t pcode_offset) { FunctionMap::Result r = function_map_.find(pcode_offset); if (r.found()) return r->value; return nullptr; } int PluginRuntime::FindNativeByName(const char *name, uint32_t *index) { size_t idx; if (!image_->FindNative(name, &idx)) return SP_ERROR_NOT_FOUND; if (index) *index = idx; return SP_ERROR_NONE; } int PluginRuntime::GetNativeByIndex(uint32_t index, sp_native_t **native) { return SP_ERROR_PARAM; } int PluginRuntime::UpdateNativeBinding(uint32_t index, SPVM_NATIVE_FUNC pfn, uint32_t flags, void *data) { if (index >= image_->NumNatives()) return SP_ERROR_INDEX; sp_native_t *native = &natives_[index]; native->pfn = pfn; native->status = pfn ? SP_NATIVE_BOUND : SP_NATIVE_UNBOUND; native->flags = flags; native->user = data; return SP_ERROR_NONE; } const sp_native_t * PluginRuntime::GetNative(uint32_t index) { if (index >= image_->NumNatives()) return nullptr; if (!natives_[index].name) natives_[index].name = image_->GetNative(index); return &natives_[index]; } uint32_t PluginRuntime::GetNativesNum() { return image_->NumNatives(); } int PluginRuntime::FindPublicByName(const char *name, uint32_t *index) { size_t idx; if (!image_->FindPublic(name, &idx)) return SP_ERROR_NOT_FOUND; if (index) *index = idx; return SP_ERROR_NONE; } int PluginRuntime::GetPublicByIndex(uint32_t index, sp_public_t **out) { if (index >= image_->NumPublics()) return SP_ERROR_INDEX; sp_public_t &entry = publics_[index]; if (!entry.name) { uint32_t offset; image_->GetPublic(index, &offset, &entry.name); entry.code_offs = offset; entry.funcid = (index << 1) | 1; } if (out) *out = &entry; return SP_ERROR_NONE; } uint32_t PluginRuntime::GetPublicsNum() { return image_->NumPublics(); } int PluginRuntime::GetPubvarByIndex(uint32_t index, sp_pubvar_t **out) { if (index >= image_->NumPubvars()) return SP_ERROR_INDEX; sp_pubvar_t *pubvar = &pubvars_[index]; if (!pubvar->name) { uint32_t offset; image_->GetPubvar(index, &offset, &pubvar->name); if (int err = context_->LocalToPhysAddr(offset, &pubvar->offs)) return err; } if (out) *out = pubvar; return SP_ERROR_NONE; } int PluginRuntime::FindPubvarByName(const char *name, uint32_t *index) { size_t idx; if (!image_->FindPubvar(name, &idx)) return SP_ERROR_NOT_FOUND; if (index) *index = idx; return SP_ERROR_NONE; } int PluginRuntime::GetPubvarAddrs(uint32_t index, cell_t *local_addr, cell_t **phys_addr) { if (index >= image_->NumPubvars()) return SP_ERROR_INDEX; uint32_t offset; image_->GetPubvar(index, &offset, nullptr); if (int err = context_->LocalToPhysAddr(offset, phys_addr)) return err; *local_addr = offset; return SP_ERROR_NONE; } uint32_t PluginRuntime::GetPubVarsNum() { return image_->NumPubvars(); } IPluginContext * PluginRuntime::GetDefaultContext() { return context_; } IPluginDebugInfo * PluginRuntime::GetDebugInfo() { return this; } IPluginFunction * PluginRuntime::GetFunctionById(funcid_t func_id) { ScriptedInvoker *pFunc = NULL; if (func_id & 1) { func_id >>= 1; if (func_id >= image_->NumPublics()) return NULL; pFunc = entrypoints_[func_id]; if (!pFunc) { entrypoints_[func_id] = new ScriptedInvoker(this, (func_id << 1) | 1, func_id); pFunc = entrypoints_[func_id]; } } return pFunc; } ScriptedInvoker * PluginRuntime::GetPublicFunction(size_t index) { assert(index < image_->NumPublics()); ScriptedInvoker *pFunc = entrypoints_[index]; if (!pFunc) { sp_public_t *pub = NULL; GetPublicByIndex(index, &pub); if (pub) entrypoints_[index] = new ScriptedInvoker(this, (index << 1) | 1, index); pFunc = entrypoints_[index]; } return pFunc; } IPluginFunction * PluginRuntime::GetFunctionByName(const char *public_name) { uint32_t index; if (FindPublicByName(public_name, &index) != SP_ERROR_NONE) return NULL; return GetPublicFunction(index); } bool PluginRuntime::IsDebugging() { return true; } void PluginRuntime::SetPauseState(bool paused) { paused_ = paused; } bool PluginRuntime::IsPaused() { return paused_; } size_t PluginRuntime::GetMemUsage() { return sizeof(*this) + sizeof(PluginContext) + image_->ImageSize() + (aligned_code_ ? code_.length : 0) + context_->HeapSize(); } unsigned char * PluginRuntime::GetCodeHash() { if (!computed_code_hash_) { MD5 md5_pcode; md5_pcode.update((const unsigned char *)code_.bytes, code_.length); md5_pcode.finalize(); md5_pcode.raw_digest(code_hash_); computed_code_hash_ = true; } return code_hash_; } unsigned char * PluginRuntime::GetDataHash() { if (!computed_data_hash_) { MD5 md5_data; md5_data.update((const unsigned char *)data_.bytes, data_.length); md5_data.finalize(); md5_data.raw_digest(data_hash_); computed_data_hash_ = true; } return data_hash_; } PluginContext * PluginRuntime::GetBaseContext() { return context_; } int PluginRuntime::ApplyCompilationOptions(ICompilation *co) { return SP_ERROR_NONE; } int PluginRuntime::LookupLine(ucell_t addr, uint32_t *line) { if (!image_->LookupLine(addr, line)) return SP_ERROR_NOT_FOUND; return SP_ERROR_NONE; } int PluginRuntime::LookupFunction(ucell_t addr, const char **out) { const char *name = image_->LookupFunction(addr); if (!name) return SP_ERROR_NOT_FOUND; if (out) *out = name; return SP_ERROR_NONE; } int PluginRuntime::LookupFile(ucell_t addr, const char **out) { const char *name = image_->LookupFile(addr); if (!name) return SP_ERROR_NOT_FOUND; if (out) *out = name; return SP_ERROR_NONE; }