Created
December 12, 2025 19:43
-
-
Save skeeto/13363b78489b26bed7485ec0d6b2c7f8 to your computer and use it in GitHub Desktop.
WNDPROC trampolines
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| .section .exebuf,"bwx" | |
| .globl exebuf | |
| exebuf: .space 1<<21 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // $ cc -mwindows -o main.exe main.c exebuf.s | |
| // This is free and unencumbered software released into the public domain. | |
| #include <assert.h> | |
| #include <stddef.h> | |
| #include <stdio.h> | |
| #include <windows.h> | |
| #define S(s) (Str){s, sizeof(s)-1} | |
| typedef struct { | |
| char *data; | |
| ptrdiff_t len; | |
| } Str; | |
| typedef struct { | |
| char *beg; | |
| char *end; | |
| } Arena; | |
| #define new(a, n, t) (t *)alloc(a, n, sizeof(t), _Alignof(t)) | |
| char *alloc(Arena *a, ptrdiff_t count, ptrdiff_t size, ptrdiff_t align) | |
| { | |
| ptrdiff_t pad = (ptrdiff_t)-(uintptr_t)a->beg & (align - 1); | |
| assert(count < (a->end - a->beg - pad)/size); | |
| char *r = a->beg + pad; | |
| a->beg += pad + count*size; | |
| return memset(r, 0, (size_t)(count*size)); | |
| } | |
| Arena get_exebuf() | |
| { | |
| extern char exebuf[1<<21]; | |
| Arena r = {exebuf, exebuf+sizeof(exebuf)}; | |
| return r; | |
| } | |
| Str clone(Arena *a, Str s) | |
| { | |
| Str r = s; | |
| r.data = new(a, r.len, char); | |
| memcpy(r.data, s.data, (size_t)r.len); | |
| return r; | |
| } | |
| typedef LRESULT Wndproc5(HWND, UINT, WPARAM, LPARAM, void *); | |
| WNDPROC make_wndproc(Arena *a, Wndproc5 proc, void *arg) | |
| { | |
| Str thunk = S( | |
| "\x48\x83\xec\x28" // sub $40, %rsp | |
| "\x48\xb8........" // movq $arg, %rax | |
| "\x48\x89\x44\x24\x20" // mov %rax, 32(%rsp) | |
| "\xe8...." // call proc | |
| "\x48\x83\xc4\x28" // add $40, %rsp | |
| "\xc3" // ret | |
| ); | |
| Str r = clone(a, thunk); | |
| int rel = (int)((uintptr_t)proc - (uintptr_t)(r.data + 24)); | |
| memcpy(r.data+ 6, &arg, sizeof(arg)); | |
| memcpy(r.data+20, &rel, sizeof(rel)); | |
| return (WNDPROC)r.data; | |
| } | |
| void set_proc_arg(WNDPROC p, void *arg) | |
| { | |
| memcpy((char *)p+6, &arg, sizeof(arg)); | |
| } | |
| LRESULT example(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp, void *arg) | |
| { | |
| printf( | |
| "%lld %lld %lld %lld \"%s\"\n", | |
| (long long)hwnd, | |
| (long long)msg, | |
| (long long)wp, | |
| (long long)lp, | |
| (char *)arg | |
| ); | |
| return 0; | |
| } | |
| LRESULT wndproc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp, void *arg) | |
| { | |
| switch (msg) { | |
| case WM_PAINT: | |
| PAINTSTRUCT ps; | |
| HDC hdc = BeginPaint(hwnd, &ps); | |
| RECT rect; | |
| GetClientRect(hwnd, &rect); | |
| char *message = arg; | |
| UINT format = DT_CENTER | DT_VCENTER | DT_SINGLELINE; | |
| DrawTextA(hdc, message, -1, &rect, format); | |
| EndPaint(hwnd, &ps); | |
| return 0; | |
| case WM_DESTROY: | |
| PostQuitMessage(0); | |
| return 0; | |
| } | |
| return DefWindowProcA(hwnd, msg, wp, lp); | |
| } | |
| int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) | |
| { | |
| Arena a = get_exebuf(); | |
| // Note: not visible from the windows subsystem | |
| WNDPROC proc = make_wndproc(&a, example, "hello world"); | |
| proc((void *)1, 2, 3, 4); | |
| set_proc_arg(proc, "goodbye"); | |
| proc((void *)5, 6, 7, 8); | |
| RegisterClassA(&(WNDCLASSA){ | |
| .lpfnWndProc = make_wndproc(&a, wndproc, "Hello World"), | |
| .lpszClassName = "demo", | |
| }); | |
| CreateWindowExA( | |
| 0, "demo", "Demo", | |
| WS_OVERLAPPED | WS_VISIBLE | WS_MINIMIZEBOX | WS_SYSMENU, | |
| CW_USEDEFAULT, CW_USEDEFAULT, | |
| 400, 200, | |
| 0, 0, 0, 0 | |
| ); | |
| for (MSG msg; GetMessageW(&msg, 0, 0, 0);) { | |
| if (msg.message == WM_QUIT) { | |
| break; | |
| } | |
| TranslateMessage(&msg); | |
| DispatchMessageW(&msg); | |
| } | |
| return 0; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment