diff options
Diffstat (limited to 'common/templates/clang')
| -rw-r--r-- | common/templates/clang/.envrc | 1 | ||||
| -rw-r--r-- | common/templates/clang/.gitignore | 58 | ||||
| -rw-r--r-- | common/templates/clang/build.zig | 66 | ||||
| -rw-r--r-- | common/templates/clang/flake.nix | 46 | ||||
| -rw-r--r-- | common/templates/clang/src/main.c | 51 | ||||
| -rw-r--r-- | common/templates/clang/src/test.zig | 37 |
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); +} |
