04827466b0
This removes one the last remnants of the SourceMod 1.0 VM implementation. The new parser introduces a number of design changes in the VM. First, the VM now takes greater responsibility for validating and sanity checking the structure of the SMX container format. Previously, malformed SMX files could easily crash SourcePawn. The loader now rejects files that have out-of-bounds offsets or incomplete sections. Complex sections, like debug info or the code stream, are verified lazily. Internally, the sp_plugin_t structure has been removed. It has been replaced by a new LegacyImage class, designed to be independent from the SPVM API. This potentially lets us load code streams from non-.smx containers. More importantly, it removes a lot of bookkeeping and pre-computed state from PluginRuntime. The LegacyImage class is now responsible for handling debug info as well. PluginRuntime is now intended to hold only cached or immutable data, and PluginContext holds all VM state. As such PluginContext is now responsible for allocating a plugin's runtime memory, not PluginRuntime. Finally, some aspects of the loading process have been cleaned up. The decompression and image handoff logic should now be easier to understand.
494 lines
11 KiB
C++
494 lines
11 KiB
C++
// 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 <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <assert.h>
|
|
#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;
|
|
}
|