Difference between revisions of "Porting to x64"

From AlliedModders Wiki
Jump to: navigation, search
(Recompiling Extensions: Mention usage of --targets)
 
Line 19: Line 19:
 
* The type you thought was an ''int'' was actually a ''size_t''!
 
* The type you thought was an ''int'' was actually a ''size_t''!
 
* The type you put down as an int or pointer was actually a ''float''!
 
* The type you put down as an int or pointer was actually a ''float''!
* Handling pointers as ''int''s when they should be ''void*'' or ''size_t'' (why would you do this??)
+
* Handling pointers as ''int''s when they should be ''void*'' or ''size_t''
 
* '''The calling convention has changed.''' Make sure to account for this in detours and patches. Make sure your prototypes are exactly correct!
 
* '''The calling convention has changed.''' Make sure to account for this in detours and patches. Make sure your prototypes are exactly correct!
 
* '''In patches:''' Did you forget the 64-bit opcode prefix when assembling your patch? EAX and RAX are accessed using different opcode prefixes!
 
* '''In patches:''' Did you forget the 64-bit opcode prefix when assembling your patch? EAX and RAX are accessed using different opcode prefixes!
Line 27: Line 27:
 
Linux uses the System-V x64 ABI. There are many great sources of documentation all over the internet: [https://wiki.osdev.org/System_V_ABI OSDev Wiki] [https://en.wikipedia.org/wiki/X86_calling_conventions#x86-64_calling_conventions Wikipedia]
 
Linux uses the System-V x64 ABI. There are many great sources of documentation all over the internet: [https://wiki.osdev.org/System_V_ABI OSDev Wiki] [https://en.wikipedia.org/wiki/X86_calling_conventions#x86-64_calling_conventions Wikipedia]
 
* All calling conventions are now an up to 8-register fastcall depending on the types (!!)
 
* All calling conventions are now an up to 8-register fastcall depending on the types (!!)
* RCX can be a thisptr, first argument, or stack return pointer depending on the context
+
* RDI can be a thisptr, first argument, or stack return pointer depending on the context
 
* '''Integer arguments and float arguments are passed in different registers!''' (generic for ints, SIMD registers for floats)
 
* '''Integer arguments and float arguments are passed in different registers!''' (generic for ints, SIMD registers for floats)
 
* Varargs are now more complex to call when floats are included in the vararg
 
* Varargs are now more complex to call when floats are included in the vararg
  
 
The exact value of return types with C++ shenanigans (destructors, etc) still needs to be figured out by someone. However, in general.
 
The exact value of return types with C++ shenanigans (destructors, etc) still needs to be figured out by someone. However, in general.
* The return argument is always passed in RCX, and will bump the thisptr to RDX (todo: confirm this)
+
* Return types bigger than 64 bits but smaller than 128 bits will be returned in RAX and RDX
* Some return types that are 128-bits will be returned in both RAX and RDX??
 
  
 
=== MSVC Calling Convention Quirks ===
 
=== MSVC Calling Convention Quirks ===
Line 39: Line 38:
  
 
* All calling conventions are now a four-register fastcall
 
* All calling conventions are now a four-register fastcall
* RCX can be a thisptr OR the first arg! It is not always a thisptr now. RCX can also be the return value (see below), bumping the thisptr to RDX.
+
* RCX is always the this pointers in member function call.
 
* '''Integer arguments and float arguments are passed in different registers!''' (generic for ints, SIMD registers for floats)
 
* '''Integer arguments and float arguments are passed in different registers!''' (generic for ints, SIMD registers for floats)
* All objects larger than 8 bytes are now passed exclusively by reference (even on the stack??)
+
* If the type doesn't have a size of 8, 16, 32 or 64 bits or has special copy operator, or copy constructor, or destructor it will be passed as pointer to a memory space that is sizeof(type).
 
* Float arguments passed to varargs need to be put in both the generic and SIMD register for the appropiate argument index. Varargs are still fastcalls... somehow...
 
* Float arguments passed to varargs need to be put in both the generic and SIMD register for the appropiate argument index. Varargs are still fastcalls... somehow...
 
* '''setjmp has new behavior''' and destroys objects as if the scope has been exited. (if you ''are'' using setjmp, good luck)
 
* '''setjmp has new behavior''' and destroys objects as if the scope has been exited. (if you ''are'' using setjmp, good luck)
 +
* The stack is always freed by the caller, never the callee
  
Return types also have their usual obscure shenanigans. More specifically,
+
If the return type is passed in memory, the pointer to that memory will be passed as the first argument to the function.
* The return argument is always passed in RCX, and will bump the thisptr to RDX (todo: confirm this)
+
* In RDX if member function (because RCX is always this pointer)
* Returns with all types larger than 8 bytes occur on the stack
+
* In RCX for everything else
* Returns with any type that has a user-defined base classes, virtual methods, constructor, destructor, or copy operation happens on the stack (''regardless of size'')
 
  
  
Line 55: Line 54:
 
=== Gamedata ===
 
=== Gamedata ===
  
All signatures will need to be updated, and likely many offsets.
+
All bytes signatures will need to be updated, mangled names stay mostly as-is.
 +
Vtable offsets won't change.
  
 
==== Gamedata key names ====
 
==== Gamedata key names ====
Line 80: Line 80:
 
=== DHooks ===
 
=== DHooks ===
  
DHooks does not support 64-bits at all for the time being. It will need heavy revisions to work under the new architecture, starting with better SourceHook support for the 64-bit architecture (see: hookmangen)
+
DHooks received partial support for 64-bits, only windows is supported. It will need heavy revisions to work under linux 64 bits, starting with better SourceHook support (see: hookmangen)
  
 
=== SDKCalls ===
 
=== SDKCalls ===
Line 88: Line 88:
 
== Recompiling Extensions ==
 
== Recompiling Extensions ==
  
The short version is that, assuming your target project uses AMBuild, pull in [https://github.com/alliedmodders/sourcemod/pull/2107 a modern revision of the build scripts], then specify the <code>--targets</code> argument to build the extension for one or more supported architectures.
+
Take a look at/copy the sample extension AMBuilder and AMBuildScript available on the sourcemod repository.
  
 
Beyond that, as long as the code isn't architecture-specific, most extensions should compile fine with 64-bit support with slight adjustments at most.  If you are using detours, you may need to [https://github.com/alliedmodders/sourcemod/blob/96727a7610a2690b20670264edb737a42f9da110/AMBuildScript#L583-L591 make further adjustments to build the new safetyhook dependency].
 
Beyond that, as long as the code isn't architecture-specific, most extensions should compile fine with 64-bit support with slight adjustments at most.  If you are using detours, you may need to [https://github.com/alliedmodders/sourcemod/blob/96727a7610a2690b20670264edb737a42f9da110/AMBuildScript#L583-L591 make further adjustments to build the new safetyhook dependency].
  
 
[[Category:SourceMod Documentation]]
 
[[Category:SourceMod Documentation]]

Latest revision as of 05:54, 6 November 2024

Overview

Many plugins are already compatible with the x64 architecture, but many that manipulate game memory (such as with hooks or patches) will require extensive revisions to run under the new 64-bit servers. Overall, if your plugin:

  • Uses the address natives or the Address type,
  • Uses dhooks
  • Uses sdkcalls or sdktools (not sdkhooks)

...you will need to update your plugin for 64 bits.

Extensions may be good to go with just a recompile for 64-bits, but some may need more extensives changes.

I'm having an issue!

In general, prototypes need to be exactly correct (including class statics like copy/destruct/constructors) as there are now significantly more quirks that will cause ABIs to be laid out differently.

Here are some things to check for that may be tripping you up:

  • The type you thought was an int was actually a size_t!
  • The type you put down as an int or pointer was actually a float!
  • Handling pointers as ints when they should be void* or size_t
  • The calling convention has changed. Make sure to account for this in detours and patches. Make sure your prototypes are exactly correct!
  • In patches: Did you forget the 64-bit opcode prefix when assembling your patch? EAX and RAX are accessed using different opcode prefixes!

Linux Calling Convention Quirks

Linux uses the System-V x64 ABI. There are many great sources of documentation all over the internet: OSDev Wiki Wikipedia

  • All calling conventions are now an up to 8-register fastcall depending on the types (!!)
  • RDI can be a thisptr, first argument, or stack return pointer depending on the context
  • Integer arguments and float arguments are passed in different registers! (generic for ints, SIMD registers for floats)
  • Varargs are now more complex to call when floats are included in the vararg

The exact value of return types with C++ shenanigans (destructors, etc) still needs to be figured out by someone. However, in general.

  • Return types bigger than 64 bits but smaller than 128 bits will be returned in RAX and RDX

MSVC Calling Convention Quirks

Microsoft has some excellent official documentation in their MSVC docs.

  • All calling conventions are now a four-register fastcall
  • RCX is always the this pointers in member function call.
  • Integer arguments and float arguments are passed in different registers! (generic for ints, SIMD registers for floats)
  • If the type doesn't have a size of 8, 16, 32 or 64 bits or has special copy operator, or copy constructor, or destructor it will be passed as pointer to a memory space that is sizeof(type).
  • Float arguments passed to varargs need to be put in both the generic and SIMD register for the appropiate argument index. Varargs are still fastcalls... somehow...
  • setjmp has new behavior and destroys objects as if the scope has been exited. (if you are using setjmp, good luck)
  • The stack is always freed by the caller, never the callee

If the return type is passed in memory, the pointer to that memory will be passed as the first argument to the function.

  • In RDX if member function (because RCX is always this pointer)
  • In RCX for everything else


Porting Plugins

Gamedata

All bytes signatures will need to be updated, mangled names stay mostly as-is. Vtable offsets won't change.

Gamedata key names

Arch/OS Linux Windows
x86 linux windows
x64 linux64 windows64

Address Natives

New address natives that support 64-bits are still under development

DHooks

DHooks received partial support for 64-bits, only windows is supported. It will need heavy revisions to work under linux 64 bits, starting with better SourceHook support (see: hookmangen)

SDKCalls

SDKCalls will need to be updated to learn how to accept a thisptr. However, all non-raw calls (eg, those to entities or gamerules that does not require a thisptr) should be fine as long as none of the arguments are 64-bits (including 64-bit ints!)

Recompiling Extensions

Take a look at/copy the sample extension AMBuilder and AMBuildScript available on the sourcemod repository.

Beyond that, as long as the code isn't architecture-specific, most extensions should compile fine with 64-bit support with slight adjustments at most. If you are using detours, you may need to make further adjustments to build the new safetyhook dependency.