Repository navigation
Conversation
… convention (cherry picked from commit a333fc9)
(cherry picked from commit dfa8659)
(cherry picked from commit ecbf53c)
(cherry picked from commit 0fca4f9)
(cherry picked from commit ed7d51f)
(cherry picked from commit 1bde664)
(cherry picked from commit d84bb88)
(cherry picked from commit daccb88)
(cherry picked from commit a998e5e)
(cherry picked from commit f3c4c07)
(cherry picked from commit 5ece3fa)
…turns Lay the arg stack out like GetStackParamOffset() (by-value objects take their own size, System V passes those over 16 bytes inline on the stack), keep the result pointer slot for Vector/string_t callbacks, return Vector in xmm0/xmm1, and make GetStackArgsSize() account for that slot on linux64 too. testsuite: cover AcceptInput (variant_t by hidden reference, fieldType offset from gamedata), Vector and float returns.
Only declared arguments got a register saved and restored, so anything else (e.g. a member function declared cdecl without its this, which works on x86 where it stays on the stack) reached the original function clobbered by the callbacks. Seen with LagCompensation's CEntityTouchManager::FrameUpdatePostEntityThink detour: segfault in the original on x86_64.
Both GenBuffers come from the same page allocator and a GenBuffer that grows marks its old block, hence the whole page, read/execute. Growing the post-callback buffer while the bridge was half written (CreatePostCallback was called from Write_ModifyReturnAddress) made the next bridge write fault once bridges got bigger.
…ns on x86_64 The raw pointer was cast to cell_t and truncated for plugins built before Address became int64.
SourceMod 1.13 has no pseudo-addresses (ISourceMod::To/FromPseudoAddress are stubs returning 0): legacy plugins got a null this/address on x86_64. Convert like LoadFromAddress & co. instead; such plugins have to be rebuilt with 1.13 to use addresses on x86_64.
|
Thanks for this work. We run it on CS:S build 11003710 x86_64: SourceMod 1.13.0.7427 with this PR, #10 and srcdslab/metamod-source#4. We hit a bug in the Linux x86_64 detour bridge. Every detoured function that returns a value hands its caller the address it returns to, which reads as Cause In
What we saw on CS:S
Void detours such as Fix The patch keeps --- a/extensions/dhooks/DynamicHooks/hook.cpp
+++ b/extensions/dhooks/DynamicHooks/hook.cpp
@@ -419,6 +419,17 @@
// Restore the previously saved registers, so any changes will be applied
Write_RestoreRegisters(jit, HOOKTYPE_POST);
+ // The return value (rax, rdx, xmm0, xmm1) must reach the caller untouched: GetReturnAddress is a
+ // C call that overwrites them, and rax then held the return address, so every non-void detoured
+ // function returned it (a "true" bool) to its caller. Kept on the stack around the call (48 bytes:
+ // the key passed to GetReturnAddress, rsp as SetReturnAddress stored it, is now rsp + 48), and the
+ // jump back goes through r11 (caller-saved, never a return register).
+ jit.push(rax);
+ jit.push(rdx);
+ jit.sub(rsp, 32);
+ jit.movsd(rsp(), xmm0);
+ jit.movsd(rsp(16), xmm1);
+
// Get return address
union
{
@@ -437,8 +448,8 @@
MSVC_ONLY(jit.mov(rcx, reinterpret_cast<std::uint64_t>(this)));
// 2nd parameter (rsp)
- GCC_ONLY(jit.lea(rsi, rsp()));
- MSVC_ONLY(jit.lea(rdx, rsp(40)));
+ GCC_ONLY(jit.lea(rsi, rsp(48)));
+ MSVC_ONLY(jit.lea(rdx, rsp(40 + 48)));
// Call GetReturnAddress
jit.mov(rax, func.address);
@@ -449,9 +460,15 @@
// Free shadow space
MSVC_ONLY(jit.add(rsp, 40));
- // Jump to the original return address
+ // Back the return value, then jump to the original return address
+ jit.mov(r11, rax);
+ jit.movsd(xmm0, rsp());
+ jit.movsd(xmm1, rsp(16));
+ jit.add(rsp, 32);
+ jit.pop(rdx);
+ jit.pop(rax);
jit.add(rsp, 8);
- jit.jump(rax);
+ jit.jump(r11);
}
void CHook::Write_CallHandler(x64JitWriter& jit, HookType_t type)Tested on CS:S 11003710 Linux x86_64
Only the Linux path was tested. The Binaries and sources The DHooks, BinTools and Metamod:Source binaries we run (this PR with the patch above, #10, srcdslab/metamod-source#4), their complete sources and a Dockerfile that rebuilds them: https://github.com/WareZ57/DHooks-x64-CSS/releases/tag/v1.0.0 |
DHooks: Linux x86_64 support
Why
Upstream DHooks skips Linux x86_64 (
AMBuilder: "Presently only x86_64 on Windows is supported"). On Linux upstream only offers it through the KHook rewrite (alliedmodders#2403, draft), which needs MM:S 2.0 and breaks every SourceHook-based extension. We need DHooks on our CS:S x64 servers now, on our pinned SourceMod (1.13.0.7427) with MM:S 1.12.What
Based on
b68a6097(1.13.0.7427, our production pin):dhookslinux64wipwork (DHooks x64 Linux enabler alliedmodders/sourcemod#2292, closed). Authorship is kept on 12 commits; one was dropped because 7427 already does the same thing with int64Addresssupport. The work covers:x86_64SystemVDefaultcalling convention for detours;Addresson x64. Plugins built beforeAddressbecame int64 pass 32-bit addresses. SourceMod 1.13 has no pseudo-addresses:ISourceMod::To/FromPseudoAddressare stubs that return 0, and the core natives just sign-extend such cells. DHooks used to C-cast those values (andthispushed to callbacks) to and from raw pointers, which doesn't build with clang on Linux x86_64. It now converts them the same way as the core natives (PtrToLegacyAddress/LegacyAddressToPtrinutil.h). That's lossless on x86. On x86_64 such plugins can't hold a pointer and must be rebuilt with SourceMod 1.13 (see the note for plugin authors below). An earlier revision of this PR mapped them through the pseudo-address stubs, which returned 0.DHookGetParamAddresstruncated the pointer for legacy plugins; it now goes through the same conversion.GenerateThunkwas a stub that returned straight away. It now rebuilds the arg stackCallback()reads, at the offsetsGetStackParamOffset()computes, fromrsi, rdx, rcx, r8, r9,xmm0-7and the caller's stack.ODTORobjects are passed by hidden reference.Callback/Callback_float/Callback_vector/Callback_stringtwith the delegate still inrdi.Vectorresults come back inxmm0/xmm1.GetStackArgsSize()skipped the result-pointer slot of Vector/string_t callbacks on linux64, so their params were copied 16 bytes short.thisof a member function declaredcdecl, which is fine on x86 where it stays on the stack. LagCompensation'sCEntityTouchManager::FrameUpdatePostEntityThinkdetour segfaulted this way.GetRegisters()now always includesrdi, rsi, rdx, rcx, r8, r9, xmm0-7.GenBufferthat grows marks its old block (so the whole page) read/execute. The post-callback was generated in the middle of the bridge, and once bridges got bigger the next bridge write faulted.plugins/testsuite/dhooks/: a CS:S runtime test (virtual hooks + detours) with its gamedata.-dhx_autoruns it unattended.Requires the SourceHook linux64 generator from srcdslab/metamod-source#4, because virtual hooks use
MakeHookManfrom MM:S core.Testing
On a CS:S x64 dedicated server (build 11003710) with bots, using
plugins/testsuite/dhooks/dhooks-css-test.sp:Teleport: params read correctly, NULL detection works, and the teleport really happens. The hook also catches the game's own respawn teleports.OnTakeDamage: the pre-hook changes the damage (applies in game) and the post-hook reads the return value.Weapon_ShootPosition(ReturnType_Vector): the post-hook reads the eye position, and a pre-hook supercede reaches the caller.GetPlayerMaxSpeed(ReturnType_Float): supercede works.AcceptInput: input name, activator,variant_t(by hidden reference) and output id are all read correctly.CCSPlayer::SwitchTeam(int)andCCSPlayer::RoundRespawn();CCSGameRules::TerminateRound(float, int): float + int params,this, and changing a param (seen by the post-hook);CGameServer::SetHibernating(bool)inengine.The whole SourceMod config of our ZE server (≈160 plugins, 14 of them using DHooks) also loads and runs on that bench without crashing.
Notes for plugin authors on x86_64:
GetEntityAddress,GameData.GetAddress,Load/StoreToAddress,SDKCall_Raw, DHooks addresses) has to be built with SourceMod 1.13 includes (64-bitAddress). Legacy builds get wrong pointers, e.g. LagCompensation crashed CS:S x64 inCCollisionProperty::MarkPartitionHandleDirty.variant_t::fieldTypeis at offset 20, not 16 (the value union grows to 16 bytes becausestring_tis a pointer). Read it from gamedata ("linux"16 /"linux64"20), as the testsuite does.SDKCallreturning aVectorby value also needs the BinTools fix (BinTools: classify every eightbyte of small objects on System V x86_64 #10) to getz.Not covered yet: small (≤ 16 bytes) objects passed by value (the System V class split is guessed as integer), detour return overrides. Next step is running our ZE plugins that use DHooks.
Only
dhooks.extis meant to be taken from this build. SourceMod core and the other extensions stay the official 7427 package.