summaryrefslogtreecommitdiff
path: root/win64call.asm
diff options
context:
space:
mode:
Diffstat (limited to 'win64call.asm')
-rw-r--r--win64call.asm174
1 files changed, 174 insertions, 0 deletions
diff --git a/win64call.asm b/win64call.asm
new file mode 100644
index 0000000..5c1c76a
--- /dev/null
+++ b/win64call.asm
@@ -0,0 +1,174 @@
+;;; Call C functions with a dynamic number of arguments x64 MSVC ;;;
+;;; Written in NASM ;;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; This is free and unencumbered software released into the public domain.
+;;
+;; Anyone is free to copy, modify, publish, use, compile, sell, or
+;; distribute this software, either in source code form or as a compiled
+;; binary, for any purpose, commercial or non-commercial, and by any
+;; means.
+;;
+;; In jurisdictions that recognize copyright laws, the author or authors
+;; of this software dedicate any and all copyright interest in the
+;; software to the public domain. We make this dedication for the benefit
+;; of the public at large and to the detriment of our heirs and
+;; successors. We intend this dedication to be an overt act of
+;; relinquishment in perpetuity of all present and future rights to this
+;; software under copyright law.
+;;
+;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+;; IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+;; OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+;; ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+;; OTHER DEALINGS IN THE SOFTWARE.
+;;
+;; For more information, please refer to <http://unlicense.org/>
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;To compile this:
+;; first get nasm: https://nasm.us/
+;; add it to your path, then do:
+;; nasm -f win64 win64call.asm
+;; You will get win64call.lib
+;;To use this in a C/C++ program:
+;; typedef void (*FnPtr)();
+;; extern unsigned long long win64_call(FnPtr fn, void *args, long long nargs);
+;; extern float win64_callf(FnPtr fn, void *args, long long nargs);
+;; extern double win64_calld(FnPtr fn, void *args, long long nargs);
+;; extern SomeType win64_call_other(FnPtr fn, void *args, long long nargs);
+;; (all of these refer to the same actual function)
+;; With MSVC's calling convention, all arguments are treated as if they were 64-bit.
+;; This means you need to convert integer arguments to unsigned long long/uint64_t before using them.
+;; So if you have integer arguments, you probably want to pass an unsigned long long * for args.
+;; Floating point arguments can either be "reinterpreted" as unsigned long longs (see 2nd example), or
+;; you can pass a double *for args instead.
+;; &((unsigned long long *)args)[i] should be a pointer to the ith argument.
+;; If you have a 1, 2, 4, or 8 byte struct argument, convert it to an integer, then pass it (keep in mind that
+;; if your struct is 8 bytes but not aligned to 8 bytes, the *(uint64_t *)&x trick will cause an unaligned read).
+;; Otherwise, pass it by pointer.
+;; For returning structs: if your type is 1, 2, 4, or 8 bytes and is POD, it will be returned as an integer.
+;; Otherwise, you need to pass a pointer to the struct as the first argument.
+;; for more info see https://docs.microsoft.com/en-us/cpp/build/x64-calling-convention
+;;So, if you want to call it:
+;; simple example (integer arguments):
+;; int foo(int a, int b, int c) {
+;; return a+b+c;
+;; }
+;; int main() {
+;; FnPtr fn = (FnPtr)foo;
+;; unsigned long long args[3] = {
+;; -1000, -3, 65
+;; };
+;; int ret = (int)win64_call(fn, args, 3);
+;; printf("%d\n", ret);
+;; }
+;; more involved example (with floating-point numbers):
+;; float bar(float a, double b, int c, double d, long e) {
+;; return a-(float)b + sinf((float)c) - (float)cos(d) + (float)e;
+;; }
+;; int main() {
+;; FnPtr fn = (FnPtr)bar;
+;; float a = -1.6f;
+;; double b = 3.0, d = 33.7;
+;; unsigned long long args[5] = {
+;; *(uint32_t *)&a, *(uint64_t *)&b, -12, *(uint64_t *)&d, 4
+;; };
+;; float ret = win64_callf(fn, args, 5);
+;; printf("%f\n", ret);
+;; }
+;;Why might you need this?
+;; Sometimes you don't know how many arguments you're gonna call a function with at compile time.
+;; For example, maybe you're making an interpreted programming language which calls out to C.
+;; (python uses libffi but that's not easy to compile...)
+;; Usually it will be with a function pointer you got from GetProcAddress.
+;;If you find a bug...
+;; Please let me know by emailing pommicket@pommicket.com.
+
+global win64_call
+global win64_callf
+global win64_calld
+global win64_call_other
+
+section .text
+
+; takes:
+; rcx - fn - pointer to function
+; rdx - args - pointer to arguments
+; r8 - nargs - number of arguments
+win64_call:
+win64_callf:
+win64_calld:
+win64_call_other:
+ ; use "shadow store" to save rsi
+ mov [rsp+24], rsi
+ mov rax, rcx ; function pointer (rcx may be overwritten)
+ mov r11, rdx ; args (rdx may be overwritten)
+ mov r10, r8 ; index_of_argument
+ mov rsi, rsp ; save original stack pointer
+
+ ; we need to make sure the stack pointer is aligned to 16 bytes when the function is called.
+ ; for some reason, even though we have to align it or stuff breaks, sometimes when
+ ; our function is called, it's not 16-byte aligned :/
+ ; find number of stack arguments:
+ cmp r8, 4
+ jg .align_stack
+ mov r8, 0 ; if there are <=4 arguments, set the number of stack arguments to 0
+.align_stack:
+ and r8, 1 ; is the number of stack arguments even or odd?
+ lea r8, [rsp+8*r8]
+ ; r8 is now equivalent to where the stack pointer will be (mod 16) when we call the function
+ and r8, 0xf ; take r8 mod 16
+ sub rsp, r8 ; align the stack pointer so when we call the function it's 16-byte aligned
+ lea r11, [r11+8*r10] ; go to end of arguments--we go from right to left
+ ; because that's the order things are pushed onto the stack
+
+ cmp r10, 0
+ je .loop_end ; no arguments
+.loop:
+ dec r10 ; --index_of_argument
+ sub r11, 8 ; --arg
+ cmp r10, 0
+ jg .after_1st
+; NOTE: we have to set both the integer and floating-point register for every argument because
+; a. we don't know if it's integer or floating point
+; b. varargs expects to have the value in both registers
+ ; 1st argument
+ mov rcx, qword [r11]
+ movsd xmm0, qword [r11]
+ jmp .continue
+.after_1st:
+ cmp r10, 1
+ jg .after_2nd
+ ; 2nd argument
+ mov rdx, qword [r11]
+ movsd xmm1, qword [r11]
+ jmp .continue
+.after_2nd:
+ cmp r10, 2
+ jg .after_3rd
+ ; 3rd argument
+ mov r8, qword [r11]
+ movsd xmm2, qword [r11]
+ jmp .continue
+.after_3rd:
+ cmp r10, 3
+ jg .after_4th
+ ; 4th argument
+ mov r9, qword [r11]
+ movsd xmm3, qword [r11]
+ jmp .continue
+.after_4th:
+ ; additional argument
+ push qword [r11]
+.continue:
+ cmp r10, 0 ; if index_of_argument > 0
+ jg .loop
+.loop_end:
+ sub rsp, 32 ; "shadow store"
+ call rax ; function pointer stored here before
+
+ mov rsp, rsi ; restore original stack pointer
+ ; restore rsi
+ mov rsi, [rsp+24]
+ ret