summaryrefslogtreecommitdiff
path: root/common/templates/clang/src
diff options
context:
space:
mode:
authorbrustybee <bee@4d2.org>2026-03-19 23:43:21 +0530
committerbrustybee <bee@4d2.org>2026-03-19 23:43:21 +0530
commit9d2256adddbb830d33119a5e01e3fad6b4b0652b (patch)
tree958e1de443a6d59a9b09716ba5815c52bfb7bfb3 /common/templates/clang/src
parentf9142a5822a4f212a3031b932cbf51b2d50344a8 (diff)
add flake templates
Diffstat (limited to 'common/templates/clang/src')
-rw-r--r--common/templates/clang/src/main.c51
-rw-r--r--common/templates/clang/src/test.zig37
2 files changed, 88 insertions, 0 deletions
diff --git a/common/templates/clang/src/main.c b/common/templates/clang/src/main.c
new file mode 100644
index 0000000..5bda38b
--- /dev/null
+++ b/common/templates/clang/src/main.c
@@ -0,0 +1,51 @@
+#define _GNU_SOURCE // Required to unlock RTLD_DEFAULT so we can search all loaded memory
+#include <ctype.h>
+#include <dlfcn.h> // Gives us dlsym() to look up memory addresses by string names
+#include <stddef.h>
+
+void hello_world(const char* action) {
+ // __func__ is secretly injected by the compiler and holds the string "hello_world".
+ // We use raw pointers here (src and dst) instead of array indices because
+ // stepping the memory address forward directly (*dst++) generates faster machine code.
+ const char* src = __func__;
+ char result[64];
+ char* dst = result;
+ int capitalize = 1;
+
+ // We walk the 'src' pointer forward until we hit the null terminator.
+ // The (dst - result) < 60 check guarantees we never write past the end of
+ // our 64-byte array, preventing stack smashing if the function name is unusually long.
+ while (*src && (dst - result) < 60) {
+ if (*src == '_') {
+ *dst++ = ' ';
+ capitalize = 1;
+ } else {
+ *dst++ = capitalize ? toupper(*src) : *src;
+ capitalize = 0;
+ }
+ src++;
+ }
+
+ // Cap off the string with a bang and the required null terminator
+ *dst++ = '!';
+ *dst++ = '\n';
+ *dst = '\0';
+
+ // Declare a function pointer that takes a format string and variadic arguments
+ int (*dynamic_eval)(const char*, ...);
+
+ // This looks unhinged, but it is the official POSIX-compliant way to use dlsym.
+ // ISO C strictly forbids casting a raw data pointer (void*) directly to a
+ // function pointer. To bypass the compiler warning, we take the address of our
+ // function pointer, cast THAT to a void**, and dereference it to write the address.
+ *(void **)(&dynamic_eval) = dlsym(RTLD_DEFAULT, action);
+
+ if (dynamic_eval) {
+ dynamic_eval("%s", result);
+ }
+}
+
+int main(void) {
+ hello_world("printf");
+ return 0;
+}
diff --git a/common/templates/clang/src/test.zig b/common/templates/clang/src/test.zig
new file mode 100644
index 0000000..49ff4d0
--- /dev/null
+++ b/common/templates/clang/src/test.zig
@@ -0,0 +1,37 @@
+const std = @import("std");
+
+extern fn hello_world(action: [*c]const u8) void;
+
+// We use global static memory for our trap.
+// Because this test runs in a single thread and we know the string is tiny,
+var captured_output: [64]u8 = undefined;
+var captured_len: usize = 0;
+
+// Exported to the dynamic symbol table so dlsym() can find it.
+// Signature must accept two pointers to perfectly map to the
+// System V ABI hardware registers used by: dynamic_eval("%s", result);
+// CPU registers (RDI and RSI) that the C code will use when calling this via variadic arguments.
+export fn test_capture_sink(fmt: [*c]const u8, msg: [*c]const u8) void {
+ // We intentionally ignore the format string ("%s") sitting in the first register
+ _ = fmt;
+
+ // std.mem.span walks the raw C pointer until it finds the \0 null terminator.
+ // It doesn't allocate memory; it just calculates the length so we have a safe Zig slice.
+ const slice = std.mem.span(msg);
+
+ // Copy the raw bytes directly from the C memory space into our static Zig buffer.
+ @memcpy(captured_output[0..slice.len], slice);
+ captured_len = slice.len;
+}
+
+test "catfish dlsym eval" {
+ // We hand our C code the name of our exported Zig function.
+ // The C code will parse its own __func__, ask the OS to find "test_capture_sink",
+ // and execute it, throwing the parsed string right back into our global variables.
+ hello_world("test_capture_sink");
+
+ // Reconstruct a strict Zig string from the exact number of bytes we captured
+ const result = captured_output[0..captured_len];
+
+ try std.testing.expectEqualStrings("Hello World!\n", result);
+}