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 Address methodmap #1841

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
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
7 changes: 6 additions & 1 deletion core/logic/PseudoAddrManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,15 @@ PseudoAddressManager::PseudoAddressManager() : m_NumEntries(0)
// A pseudo address consists of a table index in the upper 6 bits and an offset in the
// lower 26 bits. The table consists of memory allocation base addresses.
void *PseudoAddressManager::FromPseudoAddress(uint32_t paddr)
{
return FromPseudoAddress(paddr, 0);
}

void *PseudoAddressManager::FromPseudoAddress(uint32_t paddr, uint32_t offset)
{
#ifdef PLATFORM_X64
uint8_t index = paddr >> PSEUDO_OFFSET_BITS;
uint32_t offset = paddr & ((1 << PSEUDO_OFFSET_BITS) - 1);
offset += paddr & ((1 << PSEUDO_OFFSET_BITS) - 1);

if (index >= m_NumEntries)
return nullptr;
Expand Down
1 change: 1 addition & 0 deletions core/logic/PseudoAddrManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ class PseudoAddressManager
PseudoAddressManager();
public:
void *FromPseudoAddress(uint32_t paddr);
void *FromPseudoAddress(uint32_t paddr, uint32_t offset);
uint32_t ToPseudoAddress(void *addr);
private:
void *GetAllocationBase(void *ptr);
Expand Down
146 changes: 146 additions & 0 deletions core/logic/smn_core.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -816,6 +816,143 @@ enum NumberType
//memory addresses below 0x10000 are automatically considered invalid for dereferencing
#define VALID_MINIMUM_MEMORY_ADDRESS 0x10000

static inline void *GetAddress(cell_t caddr, cell_t coffset)
{
#ifdef PLATFORM_X86
return reinterpret_cast<void *>(caddr + coffset);
#else
return pseudoAddr.FromPseudoAddress((uint32_t)caddr, (uint32_t)coffset);
#endif
}

static inline bool IsAddressValidRange(void *addr)
{
return addr != NULL && reinterpret_cast<uintptr_t>(addr) >= VALID_MINIMUM_MEMORY_ADDRESS;
}

static inline int GetAddressAccess(void *addr)
{
int bits;

return SourceHook::GetPageBits(addr, &bits) ? bits : 0;
}

static inline bool SetAddressAccess(void *addr, size_t len, int access)
{
return SourceHook::SetMemAccess(addr, len, access);
}

// Very slowly if iterate by each address cell.
template <int A /* By SH_MEM_* defines. */>
static inline bool HasAddressAccess(void *addr)
{
return (GetAddressAccess(addr) & A) == A;
}

template <typename T>
static inline cell_t ReadSecureAddressCell(IPluginContext *pContext, cell_t caddr, cell_t coffset)
{
void *addr = GetAddress(caddr, coffset);

if (!IsAddressValidRange(addr))
{
#ifdef _DEBUG
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be printed regardless. What's the harm?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be printed regardless. What's the harm?

An ordinary server holder will be scared.
And from a SourceMod, there are usually not so many error written in message.

return pContext->ThrowNativeError("Invalid address 0x%x is pointing to reserved memory (base is 0x%x, offset is 0x%x, read block size is %d)", addr, caddr, coffset, sizeof(T));
#else
return pContext->ThrowNativeError("Invalid address 0x%x is pointing to reserved memory", addr);
#endif
}

#ifdef _DEBUG
if (!HasAddressAccess<SH_MEM_READ>(addr))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Safety is preferred, but I'm not sure this is necessary in either context. Address should be "trusted", and this can be a very hot path.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Safety is preferred

I put the debug build into safe operation, because direct memory take a long time with many calls. I can make a cache for quick ranges checks of allowed memory

{
return pContext->ThrowNativeError("Invalid address access by 0x%x to read memory (base is 0x%x, offset is 0x%x, read block size is %d)", addr, caddr, coffset, sizeof(T));
}
#endif

// If you have crash, enable _DEBUG for profiling which plugin the address is not valid.
return (cell_t)*reinterpret_cast<T *>(addr);
}

template <typename T>
static inline cell_t WriteSecureAddressCell(IPluginContext *pContext, cell_t caddr, cell_t coffset, cell_t cvalue)
{
void *addr = GetAddress(caddr, coffset);

if (!IsAddressValidRange(addr))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why isn't this patching memory pages like in the old implementation?

{
#ifdef _DEBUG
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same printing comment

return pContext->ThrowNativeError("Invalid address 0x%x is pointing to reserved memory (base is 0x%x, offset is 0x%x, read block size is %d)", addr, caddr, coffset, sizeof(T));
#else
return pContext->ThrowNativeError("Invalid address 0x%x is pointing to reserved memory", addr);
#endif
}

#ifdef _DEBUG
if (!HasAddressAccess<SH_MEM_READ /* Old value is being read */ | SH_MEM_WRITE>(addr))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

write the page bits regardless.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

write the page bits regardless.

From a viewpoint of dynamic memory hierarchy and a native call cost, it would be advantageous of speed and functionality to read a old value, returning it, instead of void (zero)

{
return pContext->ThrowNativeError("Invalid address access by 0x%x to write memory (base is 0x%x, offset is 0x%x, read block size is %d)", addr, caddr, coffset, sizeof(T));
}
#endif

// If you have crash, enable _DEBUG for profiling which plugin the address is not valid.
cell_t old_cvalue = (cell_t)*reinterpret_cast<T *>(addr);

*reinterpret_cast<T *>(addr) = (T)cvalue;

return old_cvalue;
}

static inline int GetSecureAddressAccessCell(IPluginContext *pContext, cell_t caddr, cell_t coffset)
{
return GetAddressAccess(GetAddress(caddr, coffset));
}

static inline cell_t SetSecureAddressAccessCell(IPluginContext *pContext, cell_t caddr, cell_t coffset, cell_t size, cell_t access)
{
return (cell_t)SetAddressAccess(GetAddress(caddr, coffset), (size_t)size, (int)access);
}

static cell_t Address_ReadInt8(IPluginContext *pContext, const cell_t *params)
{
return ReadSecureAddressCell<uint8_t>(pContext, params[1], params[2]);
}

static cell_t Address_ReadInt16(IPluginContext *pContext, const cell_t *params)
{
return ReadSecureAddressCell<uint16_t>(pContext, params[1], params[2]);
}

static cell_t Address_ReadInt32(IPluginContext *pContext, const cell_t *params)
{
return ReadSecureAddressCell<uint32_t>(pContext, params[1], params[2]);
}

static cell_t Address_WriteInt8(IPluginContext *pContext, const cell_t *params)
{
return WriteSecureAddressCell<uint8_t>(pContext, params[1], params[3], params[2]);
}

static cell_t Address_WriteInt16(IPluginContext *pContext, const cell_t *params)
{
return WriteSecureAddressCell<uint16_t>(pContext, params[1], params[3], params[2]);
}

static cell_t Address_WriteInt32(IPluginContext *pContext, const cell_t *params)
{
return WriteSecureAddressCell<uint32_t>(pContext, params[1], params[3], params[2]);
}

static cell_t Address_GetAccess(IPluginContext *pContext, const cell_t *params)
{
return (cell_t)GetSecureAddressAccessCell(pContext, params[1], params[2]);
}

static cell_t Address_SetAccess(IPluginContext *pContext, const cell_t *params)
{
return SetSecureAddressAccessCell(pContext, params[1], params[4], params[3], params[2]);
}

static cell_t LoadFromAddress(IPluginContext *pContext, const cell_t *params)
{
#ifdef PLATFORM_X86
Expand Down Expand Up @@ -1114,6 +1251,15 @@ REGISTER_NATIVES(coreNatives)
{"IsNullVector", IsNullVector},
{"IsNullString", IsNullString},
{"LogStackTrace", LogStackTrace},

{"Address.ReadInt8", Address_ReadInt8},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the existing natives should be updated to use the new functions.

{"Address.ReadInt16", Address_ReadInt16},
{"Address.ReadInt32", Address_ReadInt32},
{"Address.WriteInt8", Address_WriteInt8},
{"Address.WriteInt16", Address_WriteInt16},
{"Address.WriteInt32", Address_WriteInt32},
{"Address.GetAccess", Address_GetAccess},
{"Address.SetAccess", Address_SetAccess},

{"FrameIterator.FrameIterator", FrameIterator_Create},
{"FrameIterator.Next", FrameIterator_Next},
Expand Down
67 changes: 67 additions & 0 deletions plugins/include/sourcemod.inc
Original file line number Diff line number Diff line change
Expand Up @@ -697,6 +697,10 @@ native FeatureStatus GetFeatureStatus(FeatureType type, const char[] name);
native void RequireFeature(FeatureType type, const char[] name,
const char[] fmt="", any ...);

#define SH_MEM_READ (1 << 0) // Memory block can be read.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are implementation specific, and likely shouldn't have the SourceHook prefix. Changing them to something on the address map would likely be better.

#define SH_MEM_WRITE (1 << 1) // Memory block can be write.
#define SH_MEM_EXEC (1 << 2) // Memory block can be execute. Usually the bytecode to be executed by CPU.

/**
* Represents how many bytes we can read from an address with one load
*/
Expand All @@ -712,6 +716,69 @@ enum Address
Address_Null = 0 // a typical invalid result when an address lookup fails
};

methodmap Address
{
// Reads 1 byte from a memory address.
//
// @param offset Offset from the base address (in bytes).
// @return The value that is stored.
// @error Address is null or pointing to reserved memory.
public native any ReadInt8(int offset = 0);

// Reads 2 bytes from a memory address.
//
// @param offset Offset from the base address (in bytes).
// @return The value that is stored.
// @error Address is null or pointing to reserved memory.
public native any ReadInt16(int offset = 0);

// Reads 4 bytes from a memory address.
//
// @param offset Offset from the base address (in bytes).
// @return The value that is stored.
// @error Address is null or pointing to reserved memory.
public native any ReadInt32(int offset = 0);

// Writes 1 byte to a memory address.
//
// @param data Value to store at the address.
// @param offset Offset from the base address (in bytes).
// @return The old value that was stored.
// @error Address is null or pointing to reserved memory.
public native any WriteInt8(any data, int offset = 0);

// Writes 2 bytes to a memory address.
//
// @param data Value to store at the address.
// @param offset Offset from the base address (in bytes).
// @return The old value that was stored.
// @error Address is null or pointing to reserved memory.
public native any WriteInt16(any data, int offset = 0);

// Writes 4 bytes to a memory address.
//
// @param data Value to store at the address.
// @param offset Offset from the base address (in bytes).
// @return The old value that was stored.
// @error Address is null or pointing to reserved memory.
public native any WriteInt32(any data, int offset = 0);

// Gets accesses from a memory address block.
//
// @param offset Offset from the base address (in bytes).
// @return Access flags. See SH_MEM_* for details.
public native int GetAccess(int offset = 0);

// Sets access flags to a memory address.
// Very effectively with setting accesses for large memory block.
//
// @param access Access flags. See SH_MEM_* for details.
// @param size Memory block size to set accesses.
// @param offset Offset from the base address (in bytes).
// @return If true, accesses have been protected, otherwise failure.
public native bool SetAccess(int access, int size = 4, int offset = 0);
};

/**
* Load up to 4 bytes from a memory address.
*
Expand Down