Skip to content

Instantly share code, notes, and snippets.

@skeeto
Created December 12, 2025 19:43
Show Gist options
  • Select an option

  • Save skeeto/13363b78489b26bed7485ec0d6b2c7f8 to your computer and use it in GitHub Desktop.

Select an option

Save skeeto/13363b78489b26bed7485ec0d6b2c7f8 to your computer and use it in GitHub Desktop.
WNDPROC trampolines
.section .exebuf,"bwx"
.globl exebuf
exebuf: .space 1<<21
// $ 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