From 3ace8a04b99c69e4517e670150b63a57fa01cf14 Mon Sep 17 00:00:00 2001 From: Abhishek Enaguthi Date: Fri, 3 Jul 2026 18:48:20 -0700 Subject: [PATCH 1/6] Reset stack after longjmp Signed-off-by: Abhishek Enaguthi --- src/trace_writer.ml | 93 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 92 insertions(+), 1 deletion(-) diff --git a/src/trace_writer.ml b/src/trace_writer.ml index cb90cba03..b39cf4028 100644 --- a/src/trace_writer.ml +++ b/src/trace_writer.ml @@ -115,6 +115,7 @@ module Thread_info = struct ; inactive_callstacks : Callstack.t Stack.t ; mutable last_decode_error_time : Mapped_time.t ; ocaml_exception_state : ocaml_exception_state + ; mutable ignore_return_underflows : bool ; mutable pending_events : Pending_event.t list ; mutable pending_time : Mapped_time.t ; start_events : (Mapped_time.t * Pending_event.t) Deque.t @@ -551,6 +552,7 @@ let create_thread t event = | Some ocaml_exception_info -> With_exception_info { ocaml_exception_info; last_known_instruction_pointer = ref None }) + ; ignore_return_underflows = false ; pending_events = [] ; pending_time = Mapped_time.start_of_trace ; start_events = Deque.create () @@ -561,6 +563,7 @@ let create_thread t event = ;; let call t thread_info ~time ~location = + thread_info.ignore_return_underflows <- false; let ev = Pending_event.create_call location ~from_untraced:false in add_event t thread_info time ev; Callstack.push thread_info.callstack location @@ -569,6 +572,7 @@ let call t thread_info ~time ~location = let ret_without_checking_for_go_hacks t (thread_info : _ Thread_info.t) ~time = match Callstack.pop thread_info.callstack with | Some { symbol; _ } -> add_event t thread_info time { symbol; kind = Ret } + | None when thread_info.ignore_return_underflows -> () | None -> (* No known stackframe was popped --- could occur if the start of the snapshot started in the middle of a tracing region *) @@ -612,6 +616,86 @@ let end_of_thread t (thread_info : _ Thread_info.t) ~time ~is_kernel_address : u Thread_info.set_callstack thread_info ~is_kernel_address ~time ;; +(* C non-local jumps can return into a caller without producing the matching sequence of + branch returns for every skipped frame. Once [longjmp] is called, the current inferred + stack is no longer trustworthy. *) +module Nonlocal_jump_hacks : sig + val call_track_longjmp + : 'a inner + -> 'a Thread_info.t + -> time:Mapped_time.t + -> location:Event.Location.t + -> handled:bool +end = struct + let unversioned_symbol_name symbol = + match String.lsplit2 symbol ~on:'@' with + | None -> symbol + | Some (symbol, _) -> symbol + ;; + + let is_longjmp_symbol = function + | Symbol.From_perf symbol -> + (match unversioned_symbol_name symbol with + | "longjmp" + | "_longjmp" + | "__longjmp" + | "__libc_longjmp" + | "siglongjmp" + | "__siglongjmp" + | "__libc_siglongjmp" + | "__longjmp_chk" + | "__siglongjmp_chk" -> true + | _ -> false) + | _ -> false + ;; + + let%expect_test "recognizes longjmp symbols" = + [ "longjmp" + ; "_longjmp" + ; "__longjmp" + ; "__libc_longjmp" + ; "siglongjmp" + ; "__siglongjmp" + ; "__libc_siglongjmp" + ; "__longjmp_chk" + ; "__siglongjmp_chk" + ; "longjmp@@GLIBC_2.2.5" + ; "__longjmp_chk@@GLIBC_2.11" + ; "_setjmp" + ] + |> List.iter ~f:(fun symbol -> + printf + "%s %b\n" + symbol + (is_longjmp_symbol (Symbol.From_perf symbol))); + [%expect + {| + longjmp true + _longjmp true + __longjmp true + __libc_longjmp true + siglongjmp true + __siglongjmp true + __libc_siglongjmp true + __longjmp_chk true + __siglongjmp_chk true + longjmp@@GLIBC_2.2.5 true + __longjmp_chk@@GLIBC_2.11 true + _setjmp false + |}] + ;; + + let call_track_longjmp t thread_info ~time ~location = + if is_longjmp_symbol location.symbol + then ( + clear_all_callstacks t thread_info ~time; + thread_info.callstack <- Callstack.create ~create_time:time; + thread_info.ignore_return_underflows <- true; + true) + else false + ;; +end + (* Go (the programming language) has coroutines known as goroutines. The function [gogo] jumps from one goroutine to the next. Since [gogo] can jump anywhere, it's a shining example of what magic-trace can't handle out of the box. So, we hack it. @@ -1120,7 +1204,14 @@ and write_event' (T t) ?events_writer event = ~dst ~time; (match kind, trace_state_change with - | Some Call, (None | Some End) -> call t thread_info ~time ~location:dst + | Some Call, (None | Some End) -> + if not + (Nonlocal_jump_hacks.call_track_longjmp + t + thread_info + ~time + ~location:dst) + then call t thread_info ~time ~location:dst | ( Some ( Async | Call From 29489053115fa7621b5ea76bfd9f7e15fd523fc2 Mon Sep 17 00:00:00 2001 From: Abhishek Enaguthi Date: Fri, 3 Jul 2026 19:42:13 -0700 Subject: [PATCH 2/6] Fix longjmp helper signature Signed-off-by: Abhishek Enaguthi --- src/trace_writer.ml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/trace_writer.ml b/src/trace_writer.ml index b39cf4028..987c76b9e 100644 --- a/src/trace_writer.ml +++ b/src/trace_writer.ml @@ -625,7 +625,7 @@ module Nonlocal_jump_hacks : sig -> 'a Thread_info.t -> time:Mapped_time.t -> location:Event.Location.t - -> handled:bool + -> bool end = struct let unversioned_symbol_name symbol = match String.lsplit2 symbol ~on:'@' with From 4a4983782fdc02f87dd77a07dead34a5e58ef8b1 Mon Sep 17 00:00:00 2001 From: Abhishek Enaguthi Date: Fri, 3 Jul 2026 20:34:34 -0700 Subject: [PATCH 3/6] Apply ocamlformat to longjmp handling Signed-off-by: Abhishek Enaguthi --- src/trace_writer.ml | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/trace_writer.ml b/src/trace_writer.ml index 987c76b9e..bb0eba47f 100644 --- a/src/trace_writer.ml +++ b/src/trace_writer.ml @@ -664,10 +664,7 @@ end = struct ; "_setjmp" ] |> List.iter ~f:(fun symbol -> - printf - "%s %b\n" - symbol - (is_longjmp_symbol (Symbol.From_perf symbol))); + printf "%s %b\n" symbol (is_longjmp_symbol (Symbol.From_perf symbol))); [%expect {| longjmp true @@ -1206,11 +1203,7 @@ and write_event' (T t) ?events_writer event = (match kind, trace_state_change with | Some Call, (None | Some End) -> if not - (Nonlocal_jump_hacks.call_track_longjmp - t - thread_info - ~time - ~location:dst) + (Nonlocal_jump_hacks.call_track_longjmp t thread_info ~time ~location:dst) then call t thread_info ~time ~location:dst | ( Some ( Async From d271e072dfdd7b3e78a07ef96dcb969179e58435 Mon Sep 17 00:00:00 2001 From: Abhishek Enaguthi Date: Fri, 3 Jul 2026 21:17:18 -0700 Subject: [PATCH 4/6] Annotate longjmp thread info access Signed-off-by: Abhishek Enaguthi --- src/trace_writer.ml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/trace_writer.ml b/src/trace_writer.ml index bb0eba47f..70e2163e1 100644 --- a/src/trace_writer.ml +++ b/src/trace_writer.ml @@ -562,7 +562,7 @@ let create_thread t event = } ;; -let call t thread_info ~time ~location = +let call t (thread_info : _ Thread_info.t) ~time ~location = thread_info.ignore_return_underflows <- false; let ev = Pending_event.create_call location ~from_untraced:false in add_event t thread_info time ev; @@ -682,7 +682,7 @@ end = struct |}] ;; - let call_track_longjmp t thread_info ~time ~location = + let call_track_longjmp t (thread_info : _ Thread_info.t) ~time ~location = if is_longjmp_symbol location.symbol then ( clear_all_callstacks t thread_info ~time; From 5a5c5593174306d198b9dd012fc3794c13f2d20c Mon Sep 17 00:00:00 2001 From: Abhishek Enaguthi Date: Fri, 3 Jul 2026 21:58:38 -0700 Subject: [PATCH 5/6] Annotate longjmp location access Signed-off-by: Abhishek Enaguthi --- src/trace_writer.ml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/trace_writer.ml b/src/trace_writer.ml index 70e2163e1..5804ecc7d 100644 --- a/src/trace_writer.ml +++ b/src/trace_writer.ml @@ -682,7 +682,12 @@ end = struct |}] ;; - let call_track_longjmp t (thread_info : _ Thread_info.t) ~time ~location = + let call_track_longjmp + t + (thread_info : _ Thread_info.t) + ~time + ~(location : Event.Location.t) + = if is_longjmp_symbol location.symbol then ( clear_all_callstacks t thread_info ~time; From 5185bb90f9f0983224d47a8009426c269d2b1c30 Mon Sep 17 00:00:00 2001 From: Abhishek Enaguthi Date: Fri, 3 Jul 2026 21:58:47 -0700 Subject: [PATCH 6/6] Add temporary validation workflow --- .github/workflows/codex-validate.yml | 48 ++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 .github/workflows/codex-validate.yml diff --git a/.github/workflows/codex-validate.yml b/.github/workflows/codex-validate.yml new file mode 100644 index 000000000..bc31b7f86 --- /dev/null +++ b/.github/workflows/codex-validate.yml @@ -0,0 +1,48 @@ +name: codex-validate + +on: + push: + branches: + - 'abhishek/validate-*' + workflow_dispatch: + +jobs: + validate: + runs-on: ubuntu-latest + container: + image: alpine:latest + steps: + - name: Install apk packages + run: | + apk add autoconf bash bubblewrap build-base clang coreutils git libstdc++ \ + libstdc++-dev libunwind-static linux-headers lld llvm20 llvm20-dev llvm20-static \ + nodejs opam rsync upx zlib-static zstd-static + echo "PATH=/usr/lib/llvm20/bin:$PATH" >> "$GITHUB_ENV" + + - name: Checkout code + uses: actions/checkout@v4 + + - run: git config --global --add safe.directory "$(pwd)" + + - uses: actions/cache@v4 + with: + path: ~/.opam + key: codex-opam-5.2.0-ox-231c88c2e564fdca40e15e750aacad5fb0887435-1 + + - name: Use OCaml 5.2.0+ox + run: | + export OPAMYES=1 + export OPAMJOBS=$(($(nproc) + 2)) + export OPAMROOTISOK=1 + echo "OPAMYES=1" >> "$GITHUB_ENV" + echo "OPAMJOBS=$OPAMJOBS" >> "$GITHUB_ENV" + echo "OPAMROOTISOK=$OPAMROOTISOK" >> "$GITHUB_ENV" + + opam init --bare -yav https://github.com/ocaml/opam-repository.git + opam switch set 5.2.0+ox 2>/dev/null || \ + opam switch create 5.2.0+ox --repos "ox=git+https://github.com/oxcaml/opam-repository.git#231c88c2e564fdca40e15e750aacad5fb0887435,default" + + - run: opam install . --deps-only --locked --with-test + - run: opam exec -- dune build @fmt + - run: opam exec -- make PROFILE=static + - run: opam exec -- dune runtest --profile=static