summaryrefslogtreecommitdiff
path: root/common/templates/clang
diff options
context:
space:
mode:
Diffstat (limited to 'common/templates/clang')
-rw-r--r--common/templates/clang/.envrc1
-rw-r--r--common/templates/clang/.gitignore58
-rw-r--r--common/templates/clang/build.zig66
-rw-r--r--common/templates/clang/flake.nix46
-rw-r--r--common/templates/clang/src/main.c51
-rw-r--r--common/templates/clang/src/test.zig37
6 files changed, 259 insertions, 0 deletions
diff --git a/common/templates/clang/.envrc b/common/templates/clang/.envrc
new file mode 100644
index 0000000..3550a30
--- /dev/null
+++ b/common/templates/clang/.envrc
@@ -0,0 +1 @@
+use flake
diff --git a/common/templates/clang/.gitignore b/common/templates/clang/.gitignore
new file mode 100644
index 0000000..030089a
--- /dev/null
+++ b/common/templates/clang/.gitignore
@@ -0,0 +1,58 @@
+# Prerequisites
+*.d
+
+# Object files
+*.o
+*.ko
+*.obj
+*.elf
+
+# Linker output
+*.ilk
+*.map
+*.exp
+
+# Precompiled Headers
+*.gch
+*.pch
+
+# Libraries
+*.lib
+*.a
+*.la
+*.lo
+
+# Shared objects (inc. Windows DLLs)
+*.dll
+*.so
+*.so.*
+*.dylib
+
+# Executables
+*.exe
+*.out
+*.app
+*.i*86
+*.x86_64
+*.hex
+
+# Debug files
+*.dSYM/
+*.su
+*.idb
+*.pdb
+
+# Kernel Module Compile Results
+*.mod*
+*.cmd
+.tmp_versions/
+modules.order
+Module.symvers
+Mkfile.old
+dkms.conf
+
+# debug information files
+*.dwo
+
+.direnv
+result
diff --git a/common/templates/clang/build.zig b/common/templates/clang/build.zig
new file mode 100644
index 0000000..2953ab7
--- /dev/null
+++ b/common/templates/clang/build.zig
@@ -0,0 +1,66 @@
+const std = @import("std");
+
+pub fn build(b: *std.Build) void {
+ const target = b.standardTargetOptions(.{});
+ const optimize = b.standardOptimizeOption(.{});
+
+ const exe = b.addExecutable(.{
+ .name = "hello-c",
+ .root_module = b.createModule(.{
+ .target = target,
+ .optimize = optimize,
+ }),
+ });
+
+ // 1. Link the C standard library
+ exe.linkLibC();
+
+ // 2. Add your C source files
+ exe.addCSourceFile(.{
+ .file = b.path("src/main.c"),
+ .flags = &.{ "-Wall", "-Wextra", "-O2" },
+ });
+
+ // 3. Declare intent for the executable to be installed
+ b.installArtifact(exe);
+
+ // 4. Create the 'run' step
+ const run_step = b.step("run", "Run the app");
+ const run_cmd = b.addRunArtifact(exe);
+
+ run_step.dependOn(&run_cmd.step);
+ run_cmd.step.dependOn(b.getInstallStep());
+
+ if (b.args) |args| {
+ run_cmd.addArgs(args);
+ }
+
+ // --- 5. Support Testing ---
+ // This creates an executable that runs 'test' blocks in src/test.zig
+ const exe_unit_tests = b.addTest(.{
+ .root_module = b.createModule(.{
+ .root_source_file = b.path("src/test.zig"),
+ .target = target,
+ .optimize = optimize,
+ }),
+ });
+
+ // This exposes our Zig exported functions to the Linux
+ // dynamic symbol table so dlsym() can actually find them.
+ exe_unit_tests.rdynamic = true;
+
+ exe_unit_tests.addCSourceFile(.{
+ .file = b.path("src/main.c"),
+ .flags = &.{ "-Wall", "-Wextra", "-pedantic", "-Dmain=nomain" },
+ });
+
+ // Allow tests to link against C and find your headers
+ exe_unit_tests.linkLibC();
+ exe_unit_tests.addIncludePath(b.path("src"));
+
+ const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests);
+
+ const test_step = b.step("test", "Run unit tests");
+ test_step.dependOn(&run_exe_unit_tests.step);
+}
+
diff --git a/common/templates/clang/flake.nix b/common/templates/clang/flake.nix
new file mode 100644
index 0000000..a3dfd01
--- /dev/null
+++ b/common/templates/clang/flake.nix
@@ -0,0 +1,46 @@
+{
+ inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
+
+ outputs =
+ { self, nixpkgs, ... }:
+ let
+ inherit (nixpkgs) lib;
+ forAllSystems = lib.genAttrs lib.systems.flakeExposed;
+ pname = "hello-c";
+ in
+ {
+ packages = forAllSystems (
+ system:
+ let
+ pkgs = nixpkgs.legacyPackages.${system};
+ in
+ {
+ default = pkgs.stdenv.mkDerivation {
+ inherit pname;
+ version = "0.1.0";
+ src = self;
+
+ # The zig hook handles the build AND the install automatically
+ nativeBuildInputs = [ pkgs.zig.hook ];
+ doCheck = true;
+ };
+ }
+ );
+
+ devShells = forAllSystems (
+ system:
+ let
+ pkgs = nixpkgs.legacyPackages.${system};
+ in
+ {
+ default = pkgs.mkShellNoCC {
+ packages = [
+ pkgs.zig
+ pkgs.zls
+ pkgs.clang-tools
+ ];
+ };
+ }
+ );
+ };
+}
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);
+}