░░░                              ░░░             ▀▄
                       ▄ ▀▀▄              ░░░          ░░░
                     ▄      ▒        ▄▄                ▒    ▄▀       ▒           ▀▄
                     ▄             ▄                ▄█    █       ▄▀           ▀▀     ▀▀ ▄
                      ▀█▄▄▄▄▄██▀███▄▄      ▄▄█▄▄ ▄▀▄▄▀  ▄▄ ▄▄▄▀
                      ▀▀▄▄  █▄▀     ▄▄ ▀▀▀▀  ▀▄▄██▀▀▌  ▐          ▄█▀▀▀▄ ▄▄    ░░      ■▀ ▀▄       ▄▀▀ ██▀▄
                          ██       ▓▓     ██    ██         ▐▌██  ▄█    ██            
                          ▀█       ██░░    ▓▓  █▄     ██  ▓▓                  
                         ▓█      ▀█▄▄▄▀   ▒▒     ░░▒▒    ▒▒  ░░  ▐▌ ██      ▄▄▄          
                        █▓      ▀▀▄▄▄▄▄▓▓▀▄█   █▓  ▓  ▌▓   ▀██  ▄▄
           ▄╬▄             ▓▒       █▓     ▀▀   █▓  ▓█    █▓  ▓█ ▌  ▓▓  ▓▓█ ▄▀█▓     ▄█         ▄╬▄
      ,    █▌    ,              ▓█    ░░▄▄     ░▓  █▓ █▐        █▓    ,    ▐█    ,
        █▌    ░░▀▀▀▀ ██       ▀▄ ░░░▄▄ ░  ▓░    ▀█▐ ▐▌   ▌█  ░░         ▐█   
       ▀█▓█▓█▀    ▀▄ ░░░░     ▀▄▀▒       █▓▄▀▀▄▀░░    █▀▀▓█       ▓█ ▄▓ ▄▄   ▀█▓█▓█▀  
           ▀▓%           ▀▄     ░░     ▄▀  ▄▀     █▀  ▀█    ▀▄▄   ▄▄▀   ▒▀ █▄  ▀█    ▄▀       %▓▀      
                      ▀▄        ▄▄▀      ▄▀          ▀▄     ▄▀    ▄▄      ▄▀  ■▄                          
▀▀▄▄▄█▄▒                                             ▀█                ▄▀      ▀▄                ▒▄█▄▄▄▀▀
    ▄▄▀                                        
 ▒▄[ Arte por L.Ayres ]▄▒ 
  ▒                                                                                                                ▒ 
  █                                                                                                                █ 
 usando zig para buildar e instrumentar programas em c e c++
 ▒▒        gbrls                                                                                                   ▒▒
  ▒                                                                                                                ▒ 
 import "base.typ": base
  ▒                                                                                                                ▒ 
 ▒▒        show: base                                                                                              ▒▒
  ▒                                                                                                                ▒ 
 Zig é uma liguagem de programação e toolchain publicada inicialmente em 2016 por Andrew
 Kelly.
 ▒▒                                                                                                                ▒▒
 A linguagem em si parece mais com C e Go do que C++ e Rust, principalmente por
 causa do seu minimalismo.
  ¡                                                                                                              ▒▒
  ▒,     A Zig Software Fundation (ZSF) foi criada para manter o Zig, ela segue uma
  █▀▀°     estrutura de uma empresa sem fins lucrativos, da mesma forma que a wikipedia
 ▒▒        por exemplo.                                                                                            ▒▒
  ▒                                                                                                                ▒ 
 Na homepage do projeto Zig, um dos primeiros pontos é:
  ▒                                                                                                                ▒ 
 ▒▒        > "Melhore incrementalmente sua codebase em C/C++/Zig"                                                  ▒▒
  ▒                                                                                                                ▒ 
 Esse foi um ponto que me deixou surpreso e ate agora tem trazido muitas ideias
 de como usar Zig como mais uma ferramenta para diversao e lucro!
 ▒▒                                                                                                                ▒▒
 Um aviso importante é que Zig ainda não foi estabilizada, ou seja, cada release
 pode ter grandes mudancas na linguagem, no sistema de build, na lib std, etc.
  ▒                                                                                                                ▒ 
 ▒▒        ---                                                                                                   ¡ ▒▒
 ,
 Vamos ver esse programa simples  em C que tem um bug de _Use After Free_ (UAF).                      °▀▀
 ▒▒                                                                                                                ▒▒
 // main.c
 #include <stdio.h>
 #include <inttypes.h>
 ▒▒        #include <stdlib.h>                                                                                     ▒▒
  ▒                                                                                                                ▒ 
 void print(uint8_t* ptr) {
     for(int i = 0; i < 8; i++)
 ▒▒                printf("%x ", ptr[i]);                                                                          ▒▒
  ▒                                                                                                                ▒ 
     putchar('\n');
 }
  ¡                                                                                                              ▒▒
  ▒,     int main(void) {
  █▀▀°         uint8_t* p = malloc(100);
 ▒▒            free(p);                                                                                            ▒▒
     print(p);
  █                                                                                                                █ 
     return 0;
 ▒▒        }                                                                                                       ▒▒
  ▒                                                                                                                ▒ 
  █                                                                                                                █ 
 Existem diversas formas que poderiamos instrumentar ele para detectar este bug
 ▒▒        durante a execução do programa. Aqui vou presentar mais uma forma.                                      ▒▒
  ▒                                                                                                                ▒ 
 O script build.zig vai configurar o processo de build desse programa (o
 compilador de zig compila C e C++ com clang):
 ▒▒                                                                                                              ¡ ▒▒
 Obs: Para entender melhor como funciona o build, você pode em uma pasta vazia                        ,
 rodar zig init, um build.zig vai ser gerado com comentarios uteis, e depois                          °▀▀
 ▒▒        disso o melhor material é o proprio codigo fonte do std.Build.                                          ▒▒
  ▒                                                                                                                ▒ 
 Com o arquivo abaixo o que efetivamente estamos fazendo é usando o build.zig
 como um sistema de build para um programa todo em C:
 ▒▒                                                                                                                ▒▒
 ┌─────────┐ zig cc  ┌──────┐
 │build.zig --------> main.c│
 └─────────┘         └──────┘
 ▒▒                                                                                                                ▒▒
 // build.zig
 const std = @import("std");
  ▒                                                                                                                ▒ 
  ¡      pub fn build(b: *std.Build) void {                                                                      ▒▒
  ▒,         const target = b.standardTargetOptions(.{});
  █▀▀°         const optimize = b.standardOptimizeOption(.{});
 ▒▒                                                                                                                ▒▒
     const exe = b.addExecutable(.{
         .name = "main",
         .root_module = b.createModule(.{
 ▒▒                    .target = target,                                                                           ▒▒
             .optimize = optimize,
         }),
     });
 ▒▒                                                                                                                ▒▒
     exe.addCSourceFile(.{
         .file = b.path("main.c"),
         .flags = &.{},
 ▒▒            });                                                                                               ¡ ▒▒
 ,
     exe.linkLibC();                                                                                  °▀▀
 ▒▒                                                                                                                ▒▒
     b.installArtifact(exe);
     const run_exe = b.addRunArtifact(exe);
     const run_step = b.step("run", "run the main.c");
 ▒▒                                                                                                                ▒▒
     run_step.dependOn(&run_exe.step);
 }
  ▒                                                                                                                ▒ 
 ▒▒                                                                                                                ▒▒
 Podemos rodar esse programa com zig build run.
 Agora, vamos adicionar a instrumencação, criando um arquivo hooks.zig e
 editando o build.zig.
  ¡                                                                                                              ▒▒
  ▒,     // build.zig
  █▀▀°     const lib = b.addLibrary(.{
 ▒▒            .linkage = .static,                                                                                 ▒▒
     .name = "ziglib",
     .root_module = b.createModule(.{
         .root_source_file = b.path("hooks.zig"),
 ▒▒                .target = target,                                                                               ▒▒
         .optimize = optimize,
     }),
 });
 ▒▒        lib.linkLibC();                                                                                         ▒▒
  ▒                                                                                                                ▒ 
 // ...
  ▒                                                                                                                ▒ 
 ▒▒        exe.addCSourceFile(.{                                                                                 ¡ ▒▒
     .file = b.path("main.c"),                                                                        ,
     .flags = &.{},                                                                                   °▀▀
 ▒▒        });                                                                                                     ▒▒
  ▒                                                                                                                ▒ 
 exe.linkLibrary(lib); // é importante que a lib em zig seja linkada antes da libc
 exe.linkLibC();
 ▒▒                                                                                                                ▒▒
  ▒                                                                                                                ▒ 
 E o arquivo com a instrumentação:
  ▒                                                                                                                ▒ 
 ▒▒        // hooks.zig                                                                                            ▒▒
  ▒                                                                                                                ▒ 
 const std = @import("std");
 const dlsym = std.c.dlsym;
  ¡                                                                                                              ▒▒
  ▒,     fn getFn(name: [*:0]const u8, T: type) T {
  █▀▀°         const ptr = dlsym(@ptrFromInt(std.math.maxInt(usize)), name);
 ▒▒            const fnp: T = @ptrCast(ptr);                                                                       ▒▒
     return fnp;
 }
  ▒                                                                                                                ▒ 
 ▒▒        export fn malloc(in: usize) callconv(.c) ?*anyopaque {                                                  ▒▒
     const fnp = getFn("malloc", *const fn (usize) callconv(.c) ?*anyopaque);
     const memPtr = fnp(in);
     if (memPtr != zero) {
 ▒▒                std.debug.print("(~) [trace] malloc(bytes: 0x{x}) -> 0x{x}\n", .{ in,                           ▒▒
 @intFromPtr(memPtr) }); };
     } else {
         std.debug.print("malloc returned null\n", .{});
 ▒▒            }                                                                                                 ¡ ▒▒
 }                                                                                                    ,
 °▀▀
 ▒▒                                                                                                                ▒▒
 Agora rodando normalmente com zig build run a nossa função de malloc no
 hooks.zig vai ser utilizada ao inves do malloc da libc, mas por causa do
 dlsym [1] podemos chamar o malloc original. Esse tipo de instrumentação
 ▒▒        funciona porque estamos usando dynamic linking, por isso podemos ter o mesmo                            ▒▒
 simbolo definido em secoes diferentes.
  █                                                                                                                █ 
 Outra alternativa seria exportar a partir dos hooks a função com outro nome como
 ▒▒        zmalloc e no build adicionar uma macro para renomear o malloc. A ideia disso veio daqui                 ▒▒
 [2].
  █                                                                                                                █ 
 // build.zig
  ¡      const mod = b.createModule(.{                                                                           ▒▒
  ▒,         .target = target,
  █▀▀°         .optimize = optimize,
 ▒▒        });                                                                                                     ▒▒
 mod.addCMacro("malloc", "zmalloc");
 const exe = b.addExecutable(.{
     .name = "main",
 ▒▒            .root_module = mod,                                                                                 ▒▒
     .linkage = .static, // dessa forma o programa final pode ser estaticamente linkado
 });
  ▒                                                                                                                ▒ 
 ▒▒                                                                                                                ▒▒
 Mas quando linkar estaticamente, vamos perder o malloc original que antes podia
 ser acessado com dlsym. Apesar disso, essa forma permite que o binario final seja
 totalmente estaticamente linkado, o que desbloqueia algumas aplicacoes interessantes.
 ▒▒                                                                                                              ¡ ▒▒
 // hooks.zig                                                                                         ,
 export fn zmalloc(sz: usize) ?*anyopaque {                                                           °▀▀
 ▒▒            std.debug.print("malloc called! {x}\n", .{sz});                                                     ▒▒
     return null;
 }
  ▒                                                                                                                ▒ 
 ▒▒                                                                                                                ▒▒
 Podemos tambem adicionar hooks que vao rodar no inicio e no final da execução do
 programa. Note que esses hooks do .init_array e .fini_array (na versao atual
 do zig, 0.15.2) somente sao incluidos se tiver alguma função do hooks.zig que
 ▒▒        vai ser utilizada pelo programa C, com o malloc hookado. Valeu R3tr074 por                              ▒▒
 ter apresentado essa tecnica no feedback da primeira versao do artigo.
  █                                                                                                                █ 
 // hooks.zig
  ¡      export const init_array linksection(".init_array") = [_]*const fn () callconv(.c)                       ▒▒
  ▒,     void{&zig_init};
  █▀▀°     export const fini_array linksection(".fini_array") = [_]*const fn () callconv(.c) 
 ▒▒        void{&zig_fini};                                                                                        ▒▒
  ▒                                                                                                                ▒ 
 fn zig_init() callconv(.c) void {
     // ...
 ▒▒        }                                                                                                       ▒▒
  ▒                                                                                                                ▒ 
 fn zig_fini() callconv(.c) void {
     // ...
 ▒▒        }                                                                                                       ▒▒
  ▒                                                                                                                ▒ 
  █                                                                                                                █ 
 Voltando ao problema inicial de instrumentar o alocador, podemos usar eles para
 ▒▒        substituir o alocador da libc por qualquer outro, nesse caso vou mostrar usando                       ¡ ▒▒
 um alocador builtin do zig que tem funcionalidades para detectar leaks e limpa a                     ,
 memoria alocada para 0xaaaaaaaa... em todo free.                                                     °▀▀
 ▒▒                                                                                                                ▒▒
 Tambem vamos adicionar um hashmap para acompanhar as alocacoes
  █                                                                                                                █ 
 // hooks.zig
 ▒▒        // ...                                                                                                  ▒▒
 var gpa = std.heap.DebugAllocator(.{}){};
 const alloc = gpa.allocator();
  ▒                                                                                                                ▒ 
 ▒▒        const AllocationMap = std.AutoHashMap(usize, usize);                                                    ▒▒
 var allocations: AllocationMap = undefined;
 var allocations_mutex: std.Thread.Mutex = .{};
  ▒                                                                                                                ▒ 
  ¡      fn zig_init() callconv(.c) void {                                                                       ▒▒
  ▒,         allocations = AllocationMap.init(gpa);
  █▀▀°     }
 ▒▒                                                                                                                ▒▒
 // ...
 export fn malloc(in: usize) callconv(.c) ?*anyopaque {
 // ...
 ▒▒            const slice = alloc.alloc(u8, in) catch {                                                           ▒▒
         @panic("Failed to allocate");
     };
     // ...
 ▒▒                                                                                                                ▒▒
     allocations_mutex.lock();
     defer allocations_mutex.unlock();
  ▒                                                                                                                ▒ 
 ▒▒            allocations.put(@intFromPtr(slice.ptr), size) catch {                                             ¡ ▒▒
         gpa.free(slice);                                                                             ,
         return null;                                                                                 °▀▀
 ▒▒            };                                                                                                  ▒▒
     return @ptrCast(slice.ptr);
 }
  ▒                                                                                                                ▒ 
 ▒▒        export fn free(ptr: ?*anyopaque) callconv(.c) void {                                                    ▒▒
     // ...
     allocations_mutex.lock();
     defer allocations_mutex.unlock();
 ▒▒                                                                                                                ▒▒
     const ptrVal: usize = @intFromPtr(ptr);
  █                                                                                                                █ 
     if (allocations.get(ptrVal)) |size| {
  ¡              const slice: []u8 = @as([*]u8, @ptrCast(ptr))[0..size]; // esse cast é necessario               ▒▒
  ▒,     porque esse alocador retornou um slice, ao inves de um ponteiro cru.
  █▀▀°             alloc.free(slice);
 ▒▒                _ = allocations.remove(ptrVal);                                                                 ▒▒
     }
 }
  ▒                                                                                                                ▒ 
 ▒▒        // ...                                                                                                  ▒▒
  ▒                                                                                                                ▒ 
 fn zig_fini() callconv(.c) void {
     std.debug.print("\n===== [fini hook]\n", .{});
 ▒▒                                                                                                                ▒▒
     // ...
     allocations.deinit(); // Se não liberar aqui, essa memoria vai contar como vazada pelo 
 alocador.
 ▒▒            std.debug.print("leaks: {any}\n", .{gpa.detectLeaks()});                                          ¡ ▒▒
 }                                                                                                    ,
 °▀▀
 ▒▒                                                                                                                ▒▒
 Agora alterando o main.c para não dar free
  █                                                                                                                █ 
 int main(void) {
 ▒▒            uint8_t* p = malloc(100);                                                                           ▒▒
     // free(p);
  █                                                                                                                █ 
     return 0;
 ▒▒        }                                                                                                       ▒▒
  ▒                                                                                                                ▒ 
  █                                                                                                                █ 
 Quando rodar o programa vamos ter:
  ¡                                                                                                              ▒▒
  ▒,     [init hook]
  █▀▀°     (~) [trace] malloc(bytes: 0x64) -> 7fb8701e0000
 ▒▒                                                                                                                ▒▒
 ===== [fini hook]
 error(gpa): memory address 0x7fb8701e0000 leaked:
 .../hooks.zig:49:38: 0x1130425 in malloc (hooks.zig)
 ▒▒                    const slice = alloc.alloc(u8, in) catch {                                                   ▒▒
                                      ^
 .../main.c:13:18: 0x1025cc8 in main (.../main.c)
     uint8_t* p = malloc(100);
 ▒▒                         ^                                                                                      ▒▒
 ???:?:?: 0x7fb87020d5b4 in ??? (libc.so.6)
 ???:?:?: 0x7fb87020d667 in ??? (libc.so.6)
 ???:?:?: 0x1025b94 in ??? (???)
 ▒▒                                                                                                              ¡ ▒▒
 leaks: true                                                                                          ,
 °▀▀
 ▒▒                                                                                                                ▒▒
 O alocador mostra onde foi alocada a memoria que vazou e ate o stack trace
 mostrando o source de Zig e C! Para entender como essas funcionalidades foram
 implementadas o source é bem organizado e legivel [3], mesmo que você não tenha
 ▒▒        familiaridade com Zig [4][5][6].                                                                        ▒▒
  ▒                                                                                                                ▒ 
 Aqui no final, é o codigo inteiro do hook.zig para o programa C dinamicamente
 linkado com algumas funcionalidades extras, como mostrando o stacktrace em toda
 ▒▒        call de free e malloc.                                                                                  ▒▒
  ▒                                                                                                                ▒ 
 const std = @impor et("std");
 const dlsym = st alocada para 0xaaaaaaaa...d.c.dlsym;
  ¡                                                                                                              ▒▒
  ▒,     var gpa = std.heap.DebugAllocator(.{}){};
  █▀▀°     const alloc = gpa.allocator();
 ▒▒                                                                                                                ▒▒
 const use_libc_alloc = false;
 const verbose = false;
  ▒                                                                                                                ▒ 
 ▒▒        const AllocationMap = std.AutoHashMap(usize, usize);                                                    ▒▒
 var allocations: AllocationMap = undefined;
 var allocations_mutex: std.Thread.Mutex = .{};
  ▒                                                                                                                ▒ 
 ▒▒        export const init_array linksection(".init_array") = [_]*const fn () callconv(.c)                       ▒▒
 void{&zig_init};
 export const fini_array linksection(".fini_array") = [_]*const fn () callconv(.c) 
 void{&zig_fini};
 ▒▒                                                                                                              ¡ ▒▒
 fn zig_init() callconv(.c) void {                                                                    ,
     std.debug.print("[init hook]\n", .{});                                                           °▀▀
 ▒▒            allocations = AllocationMap.init(alloc);                                                            ▒▒
 }
  █                                                                                                                █ 
 fn zig_fini() callconv(.c) void {
 ▒▒            std.debug.print("\n===== [fini hook]\n", .{});                                                      ▒▒
  ▒                                                                                                                ▒ 
     if (!use_libc_alloc) {
         allocations.deinit();
 ▒▒                std.debug.print("leaks: {any}\n", .{gpa.detectLeaks()});                                        ▒▒
     } else {
         defer allocations.deinit();
         var it = allocations.iterator();
  ¡              const leaks = it.next() != null;                                                                ▒▒
  ▒,             std.debug.print("leaks: {any}\n", .{leaks});
  █▀▀°             if (leaks) print_alloc_state();
 ▒▒            }                                                                                                   ▒▒
 }
  █                                                                                                                █ 
 fn getFn(name: [*:0]const u8, T: type) T {
 ▒▒            const ptr = dlsym(@ptrFromInt(std.math.maxInt(usize)), name);                                       ▒▒
     const fnp: T = @ptrCast(ptr);
     return fnp;
 }
 ▒▒                                                                                                                ▒▒
 export fn malloc(in: usize) callconv(.c) ?*anyopaque {
     const fnp = getFn("malloc", *const fn (usize) callconv(.c) ?*anyopaque);
     const memPtr: ?*anyopaque = blk: {
 ▒▒                if (use_libc_alloc) {                                                                         ¡ ▒▒
             break :blk fnp(in);                                                                      ,
         } else {                                                                                     °▀▀
 ▒▒                    const slice = alloc.alloc(u8, in) catch {                                                   ▒▒
                 @panic("Failed to allocate");
             };
             break :blk @ptrCast(slice.ptr);
 ▒▒                }                                                                                               ▒▒
     };
  █                                                                                                                █ 
     const zero: ?*anyopaque = @ptrFromInt(0);
 ▒▒                                                                                                                ▒▒
     if (memPtr != zero) {
         std.debug.print("(~) [trace] malloc(bytes: 0x{x}) -> {x}\n", .{ in, 
 @intFromPtr(memPtr) });
  ¡              allocations_mutex.lock();                                                                       ▒▒
  ▒,             defer allocations_mutex.unlock();
  █▀▀°             allocations.put(@intFromPtr(memPtr), in) catch {
 ▒▒                    @panic("Failed to update allocations map");                                                 ▒▒
         };
     } else {
         std.debug.print("malloc returned null\n", .{});
 ▒▒            }                                                                                                   ▒▒
     if (verbose) {
         print_alloc_state();
         print_stacktrace();
 ▒▒            }                                                                                                   ▒▒
  ▒                                                                                                                ▒ 
     return memPtr;
 }
 ▒▒        export fn free(ptr: ?*anyopaque) callconv(.c) void {                                                  ¡ ▒▒
     const fnp = getFn("free", *const fn (?*anyopaque) callconv(.c) void);                            ,
     std.debug.print("(~) [trace] free(ptr: {x})\n", .{@intFromPtr(ptr)});                            °▀▀
 ▒▒                                                                                                                ▒▒
     if (verbose) {
         print_alloc_state();
         print_stacktrace();
 ▒▒            }                                                                                                   ▒▒
  ▒                                                                                                                ▒ 
     if (use_libc_alloc) {
         fnp(ptr);
 ▒▒            } else {                                                                                            ▒▒
         const ptrVal: usize = @intFromPtr(ptr);
         allocations_mutex.lock();
         defer allocations_mutex.unlock();
  ¡              if (allocations.get(ptrVal)) |size| {                                                           ▒▒
  ▒,                 const slice: []u8 = @as([*]u8, @ptrCast(ptr))[0..size];
  █▀▀°                 alloc.free(slice);
 ▒▒                    _ = allocations.remove(ptrVal);                                                             ▒▒
         }
     }
 }
 ▒▒                                                                                                                ▒▒
 fn print_alloc_state() void {
     std.debug.print("\n=== allocator state: \n", .{});
  ▒                                                                                                                ▒ 
 ▒▒            var it = allocations.iterator();                                                                    ▒▒
     while (it.next()) |entry| {
         std.debug.print("key = {x}, value = {any}\n", .{ entry.key_ptr.*, entry.value_ptr.* 
 });
 ▒▒            }                                                                                                 ¡ ▒▒
 }                                                                                                    ,
 °▀▀
 ▒▒        inline fn print_stacktrace() void {                                                                     ▒▒
     std.debug.print("[stacktrace begin] =====\n", .{});
     const stderr = std.debug.lockStderrWriter(&.{});
     defer std.debug.unlockStderrWriter();
 ▒▒                                                                                                                ▒▒
     std.debug.dumpCurrentStackTraceToWriter(@returnAddress(), stderr) catch {};
     std.debug.print("[stacktrace end] =====\n\n", .{});
 }
 ▒▒                                                                                                                ▒▒
  ▒                                                                                                                ▒ 
 Nesses exemplos anteriores, Zig é usado como um uma "peca extra" que é
 encaixada ao programa principal que é escrito em C. Tambem podemos fazer o
  ¡      contrario, usar Zig como o programa principal e incluir funcionalidades de um                           ▒▒
  ▒,     programa C.
  █▀▀°
 ▒▒        O build.zig na pasta do quickjs serve para compilar o quickjs e podermos incluir                        ▒▒
 ele do harness como um modulo. Fazer desse jeito não é necessario, poderiamos
 ter somente um build.zig, mas eu achei que fica mais organizado dessa forma.
  ▒                                                                                                                ▒ 
 ▒▒        Os arquivos estao organizados assim:                                                                    ▒▒
  ▒                                                                                                                ▒ 
 ./quickjs/
 ./quickjs/qjs.c
 ▒▒        ./quickjs/quickjs.h                                                                                     ▒▒
 ./quickjs/build.zig
 ...etc
 ./
 ▒▒        ./build.zig                                                                                           ¡ ▒▒
 ./build.zon                                                                                          ,
 ./harness.zig                                                                                        °▀▀
 ▒▒                                                                                                                ▒▒
  ▒                                                                                                                ▒ 
 Esse arquivo abaixo é o build.zig para buildar a engine quickjs. é um pouco de
 uma gambiarra porque eu ignorei os arquivos que produzem os entrypoints do
 ▒▒        programa, dessa forma podemos usar como uma biblioteca dinamica.                                        ▒▒
  ▒                                                                                                                ▒ 
  █                                                                                                                █ 
 Note tambem o addTranslateC que vai traduzir um arquivo C para Zig (funciona
 ▒▒        bem na maioria dos casos), isso nos vai permitir incluir funcoes o quickjs com                          ▒▒
 os tipos corretamente traduzidos, como se fosse nativa do Zig.
  █                                                                                                                █ 
 // ./quickjs/build.zig
  ¡      pub fn build(b: *std.Build) void {                                                                      ▒▒
  ▒,         const target = b.standardTargetOptions(.{});
  █▀▀°         const optimize = b.standardOptimizeOption(.{});
 ▒▒                                                                                                                ▒▒
     const header = b.addTranslateC(.{ // aqui
         .root_source_file = b.path("qjs.c"),
         .target = target,
 ▒▒                .optimize = optimize,                                                                           ▒▒
     });
  █                                                                                                                █ 
     const mod = b.addModule("qjs", .{
 ▒▒                .target = target,                                                                               ▒▒
         .optimize = optimize,
         .root_source_file = header.getOutput(),
     });
 ▒▒                                                                                                              ¡ ▒▒
     mod.addCSourceFiles(.{                                                                           ,
         .files = &.{                                                                                 °▀▀
 ▒▒                    // "qjs.c",                                                                                 ▒▒
             // "qjsc.c",
             "quickjs-libc.c",
             "quickjs.c",
 ▒▒                    "cutils.c",                                                                                 ▒▒
             "dtoa.c",
             "libunicode.c",
             "libregexp.c",
 ▒▒                },                                                                                              ▒▒
         .flags = &.{
             // "-fsanitize=address",
             "-Wall",
  ¡                  "-Wextra",                                                                                  ▒▒
  ▒,                 "-D_GNU_SOURCE",
  █▀▀°                 "-DCONFIG_VERSION=\"2021-03-27\"",
 ▒▒                    // "-DDUMP_BYTECODE",                                                                       ▒▒
             // "-DDUMP_FREE",
             // "-DDUMP_SHAPES",
             // "-DDUMP_MEM",
 ▒▒                    // "-DDUMP_OBJECTS",                                                                        ▒▒
             // "-DDUMP_GC",
         },
     });
 ▒▒                                                                                                                ▒▒
     const obj = b.addLibrary(.{
         .root_module = mod,
         .name = "quickjslib_ziggy",
 ▒▒                .linkage = .dynamic,                                                                          ¡ ▒▒
     });                                                                                              ,
     // obj.linkLibC();                                                                               °▀▀
 ▒▒            obj.linkSystemLibrary("m");                                                                         ▒▒
     obj.linkSystemLibrary("dl");
     obj.linkSystemLibrary("pthread");
  ▒                                                                                                                ▒ 
 ▒▒            b.installArtifact(obj);                                                                             ▒▒
  ▒                                                                                                                ▒ 
     //
     // exe.linkSystemLibrary("asan");
 ▒▒        }                                                                                                       ▒▒
 // ...
  █                                                                                                                █ 
  ▒                                                                                                                ▒ 
  ¡      E no ./build.zon adicionamos esse quickjs como uma dependencia local                                    ▒▒
  ▒,
  █▀▀°     .{
 ▒▒            // ...                                                                                              ▒▒
     .dependencies = .{
         .qjs = .{
             .path = "./quickjs",
 ▒▒                },                                                                                              ▒▒
     },
     // ...
 }
 ▒▒                                                                                                                ▒▒
  ▒                                                                                                                ▒ 
 E no ./build.zig centralizamos a build, o qjs e o harness
  ▒                                                                                                                ▒ 
 ▒▒        pub fn build(b: *std.Build) void {                                                                    ¡ ▒▒
     const target = b.standardTargetOptions(.{});                                                     ,
     const optimize = b.standardOptimizeOption(.{});                                                  °▀▀
 ▒▒                                                                                                                ▒▒
     const qjs = b.dependency("qjs", .{ .target = target, .optimize = optimize });
     const harness_mod =
         b.createModule(.{
 ▒▒                    .root_source_file = b.path("harness.zig"),                                                  ▒▒
             .target = target,
             .optimize = optimize,
         });
 ▒▒                                                                                                                ▒▒
     harness_mod.addImport("qjs", qjs.module("qjs"));
     const harness_exe = b.addExecutable(.{ .name = "harness", .root_module = harness_mod });
     harness_exe.linkSystemLibrary("m");
  ¡          harness_exe.linkSystemLibrary("dl");                                                                ▒▒
  ▒,         harness_exe.linkSystemLibrary("pthread");
  █▀▀°
 ▒▒            b.installArtifact(harness_exe);                                                                     ▒▒
     const run_exe = b.addRunArtifact(harness_exe);
     const run_step = b.step("run", "Run the app");
     run_step.dependOn(&run_exe.step);
 ▒▒                                                                                                                ▒▒
     run_step.dependOn(b.getInstallStep());
 }
  ▒                                                                                                                ▒ 
 ▒▒                                                                                                                ▒▒
 Com isso configurado, podemos usar as funcoes do quickjs como se estivessemos escrevendo em
 C
  ▒                                                                                                                ▒ 
 ▒▒        const qjs = @import("qjs");                                                                           ¡ ▒▒
 //  ...                                                                                              ,
 °▀▀
 ▒▒        // vai guardar e serializar o resultao do testes                                                        ▒▒
 const EvalRecord = struct {
     input: [:0]u8,
     output: []u32,
 ▒▒        };                                                                                                      ▒▒
  ▒                                                                                                                ▒ 
 // ...
  ▒                                                                                                                ▒ 
 ▒▒        fn eval_string(data: [:0]u8) ?EvalRecord {                                                              ▒▒
     var tmp: u64 = undefined;
     const vtable: qjs.JSMallocFunctions = .{ .js_malloc = js_malloc_hook, .js_free = 
 js_free_hook, .js_realloc = js_realloc_hook };
  ¡                                                                                                              ▒▒
  ▒,         const rt = qjs.JS_NewRuntime2(&vtable, @ptrCast(&tmp));
  █▀▀°         const ctx = qjs.JS_NewContext(rt);
 ▒▒            const value = qjs.JS_Eval(ctx, data, data.len, "<none>", qjs.JS_EVAL_TYPE_GLOBAL);                  ▒▒
     const nbytes = 0x100;
  █                                                                                                                █ 
     switch (value.tag) {
 ▒▒                qjs.JS_TAG_INT => std.debug.print("obj is int: {}\n", .{value.u.int32}),                        ▒▒
         qjs.JS_TAG_EXCEPTION => std.debug.print("obj is exception: {any}\n", 
 .{value.u.ptr}),
         qjs.JS_TAG_OBJECT, qjs.JS_TAG_STRING => {
 ▒▒                    std.debug.print("obj is obj/string: {any}\n", .{value.u.ptr});                              ▒▒
             litdbg.mem(u64, value.u.ptr, nbytes) catch {};
  █                                                                                                                █ 
             const objptr: usize = @intFromPtr(value.u.ptr);
 ▒▒                    const memptr: [*]u32 = @ptrCast(@alignCast(value.u.ptr));                                 ¡ ▒▒
             const teleptr: [*]usize = @ptrCast(@alignCast(value.u.ptr));                             ,
 °▀▀
 ▒▒                    const shape = teleptr[1];                                                                   ▒▒
             const prop = teleptr[2];
  █                                                                                                                █ 
             @breakpoint();
 ▒▒                    std.debug.print("shape: {x}, prop: {x}\n", .{ shape, prop });                               ▒▒
             debug_print_mem(@ptrFromInt(objptr), 0x10);
  █                                                                                                                █ 
             return .{ .input = data, .output = memptr[0..0x18] };
 ▒▒                },                                                                                              ▒▒
         qjs.JS_TAG_UNDEFINED => std.debug.print("obj is undefined\n", .{}),
         else => std.debug.print("obj is unknown tag: {}\n", .{value.tag}),
     }
  ¡                                                                                                              ▒▒
  ▒,         return null;
  █▀▀°     }
 ▒▒                                                                                                                ▒▒
 // ...
  █                                                                                                                █ 
 fn logTest(comptime name: []const u8, comptime input: []const u8) !void {
 ▒▒            const res = eval_string(@ptrCast(@constCast(input)));                                               ▒▒
  ▒                                                                                                                ▒ 
     const f = try std.fs.cwd().createFile(name, .{});
  ▒                                                                                                                ▒ 
 ▒▒            var json_writer = std.Io.Writer.Allocating.init(std.heap.page_allocator);                           ▒▒
     defer json_writer.deinit();
     try std.json.Stringify.value(res, .{}, &json_writer.writer);
     const arr = try json_writer.toOwnedSliceSentinel(0);
 ▒▒                                                                                                              ¡ ▒▒
     const written = try f.write(arr);                                                                ,
 °▀▀
 ▒▒            std.debug.print("written {} bytes: {s}\n", .{ written, arr });                                      ▒▒
 }
  █                                                                                                                █ 
 // ...
 ▒▒                                                                                                                ▒▒
 pub fn main() !void {
     try logTest("basic_string.json", "let str = \"gwimbly\"; str");
     try logTest("basic_obj.json", "let obj = {a: 42, b: 1337}; obj");
 ▒▒            try logTest("basic_arrbuf.json", "let arrbuf = new ArrayBuffer(0xcafe); arrbuf");                   ▒▒
 }
  █                                                                                                                █ 
  ▒                                                                                                                ▒ 
  ¡      Dessa forma temos um harness para criar o runtime do quickjs, enviar inputs e                           ▒▒
  ▒,     observar os resultados. Uma grande facilidade é que o Zig tem serialização JSON
  █▀▀°     builtin, que usei na intrumentação para salvar no disco os testes. Outra
 ▒▒        funcionalidade interessante é que podemos rodar com a flag --watch, assim:                              ▒▒
 - zig build run --watch
 Isso vai recompilar e rodar o programa toda vez que tiver uma modificação no
 codigo, isso é muito util para criar um loop de feedback quase instantaneo do
 ▒▒        resultado dos testes. Essa ideia eu exploro mais no apendice desse artigo.                              ▒▒
  ▒                                                                                                                ▒ 
  █                                                                                                                █ 
 No final o que montamos foi uma instrumentação em userspace que é adicionada
 ▒▒        diretamente no executavel e que é completamente customizavel. E alem disso,                             ▒▒
 o processo de build, linking (e ate mais coisas que não form abordadas aqui)
 foi organizado por um so arquivo build.zig, tem um bonus tambem que essa
 toolchain permite cross compilation entre arquiteturas e sistemas operacionais.
 ▒▒                                                                                                              ¡ ▒▒
 ,
 Alem do que foi apresentado aqui, o Zig ainda tem algumas coisas que acho bem                        °▀▀
 ▒▒        promissoras, mas que não tive contato o suficiente para apresentar aqui, como um                        ▒▒
 novo fuzzer implementado do zero integrado na toolchain do Zig [7].
  █                                                                                                                █ 
  ▒                                                                                                                ▒ 
 ▒▒        [1] - https://linux.die.net/man/3/dlsym                                                                 ▒▒
 [2] -
 https://ziggit.dev/t/zalloc-replace-malloc-and-friends-with-a-zig-allocator-in-your-c-code/11073
 [3] - https://codeberg.org/ziglang/zig/src/branch/master/lib/std/heap/debug_allocator.zig
 ▒▒        [4] - https://ziglang.org/learn/                                                                        ▒▒
 [5] - https://ziglang.org/documentation/master/std/
 [6] - https://ziglang.org/documentation/master/
 [7] - https://github.com/ziglang/zig/issues/20702
  ¡      [8] - https://typst.app/                                                                                ▒▒
  ▒,     ---
  █▀▀°
 ▒▒        [apendice]                                                                                              ▒▒
  ▒                                                                                                                ▒ 
 Depois do fim tem nada, mas o nada se contrai, cria um frasco vazio, que entao
 é preenchido. Aqui é esse lugar.
 ▒▒                                                                                                                ▒▒
 A parte do artigo que fala sobre rodar a instrumentaco com zig build run
 --watch faz parte de uma ideia maior, que é "Literate Debugging".
  ▒                                                                                                                ▒ 
 ▒▒        Essa ideia foi fortemente baseada no conceito de "Literate Programming"                                 ▒▒
 introduzido pelo Donald Knuth.
  █                                                                                                                █ 
 A ideia de "Literate Debugging" é que geralmente quando estamos debugando
 ▒▒        algo estamos escrevendo programas, em gdbscript, python ou alguma outra coisa                         ¡ ▒▒
 o objetivo final deles é mostrar a execução do programa para uma pessoa                              ,
 (geralmente nos mesmos). A ideia de puxar o literario aqui, é que foco é                             °▀▀
 ▒▒        movido para a pessoa que esta lendo o resultado.                                                        ▒▒
  ▒                                                                                                                ▒ 
 Eu fui atraido por essa ideia por causa de situacoes como escrever um blog ou
 uns slides com diagramas mostrando como a a pilha de um programa se comportava
 ▒▒        ao longo de uns pontos especificos da sua execução. Um jeito poderia ser printar                        ▒▒
 o gdb e colar as imagens, mas eu não queria imagens pixeladas, outro jeito
 melhor poderia ser logar a saida do GDB para um arquivo com `set logging enabled
 on`, mas ainda podemos fazer melhor.
 ▒▒                                                                                                                ▒▒
 O harness para o quickjs que mostrei na seção principal do artigo, gera um
 arquivo json para cada teste, a cada modificação do arquivo por causa do --watch.
 Para montar uma visualização disso para  uns slides ou um blog, eu
  ¡      posso gerar um pdf, svg, html, etc dinamicamente baseado nesse json.                                    ▒▒
  ▒,
  █▀▀°     Uma ferramenta que tenho gostado bastante para isso tem sido o typst [8] (com
 ▒▒        ele podemos escrever um codigo que parece com markdown e latex que gera                                 ▒▒
 documentos estaticos).
  █                                                                                                                █ 
 Isso aqui é como ficou o codigo em typst para ler o json e mostrar em uma tabela
 ▒▒                                                                                                                ▒▒
 #let showLog(src) = {
   let basic_str = json(src)
   let hex_32 = (
 ▒▒            basic_str                                                                                           ▒▒
       .output
       .chunks(2)
       .enumerate()
 ▒▒              .map(((i, n)) => {                                                                              ¡ ▒▒
         if (calc.rem((i + 1), 2) == 0) {                                                             ,
           set text(fill: luma(100%))                                                                 °▀▀
 ▒▒                  raw(to_hex(i * 8, width: 2) + " " + to_hex_list(n))                                           ▒▒
         } else {
           set text(fill: luma(70%))
           raw(to_hex(i * 8, width: 2) + " " + to_hex_list(n))
 ▒▒                }                                                                                               ▒▒
       })
   )
  ▒                                                                                                                ▒ 
 ▒▒          (                                                                                                     ▒▒
     code: raw(block: true, lang: "js", basic_str.input),
     hex: grid(
       inset: 2pt,
  ¡            fill: code_bg,                                                                                    ▒▒
  ▒,           ..hex_32
  █▀▀°         ),
 ▒▒          )                                                                                                     ▒▒
 }
  █                                                                                                                █ 
 #let logs = (
 ▒▒          showLog("basic_arrbuf.json"),                                                                         ▒▒
   showLog("basic_string.json"),
   showLog("basic_obj.json"),
 )
 ▒▒                                                                                                                ▒▒
 #block(fill: code_bg)[
   #grid(
     columns: 3,
 ▒▒            gutter: 8pt,                                                                                      ¡ ▒▒
     ..logs.map(log => log.code),                                                                     ,
     ..logs.map(log => log.hex)                                                                       °▀▀
 ▒▒          )                                                                                                     ▒▒
 ]
  █                                                                                                                █ 
  ▒                                                                                                                ▒ 
 ▒▒        Uma coisa que funcionou muito bem com essa configuração é que eu posso rodar                            ▒▒
 zig build run --watch e typst watch qjs_debug.typ entao toda vez que eu
 criar um teste ou modificar alguma coisa no harness.zig, o pdf vai ser
 atualizao instantaneamente com os novos resultados.
 ▒▒                                                                                                                ▒▒
 Acredito que essa ideia tem muito potencial, ainda podemos fazer muito com
 ela, como: desenvolver novas ferramentas, criar material didatico, ou ate mesmo
 so a filosofia de que o que fazemos no final é transmitir ideias para pessoas.
 ▒▒                                                                                                                ▒▒
  ▒                                                                                                                ▒ 
 ▒▒▒▒▒ ░░                                                                                                    ▒▒▒▒▒▒▒▒
                                                                                                                  ▓▄█
                                      T R A M O I A   ·   Z I N E   ·  2 0 2 6                                 gld ██
  ▀▄▄▄▄                                                                                                          ▒▒▄▄▀