Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions .github/workflows/codex-validate.yml
Original file line number Diff line number Diff line change
@@ -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
93 changes: 91 additions & 2 deletions src/trace_writer.ml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 ()
Expand All @@ -560,7 +562,8 @@ 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;
Callstack.push thread_info.callstack location
Expand All @@ -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 *)
Expand Down Expand Up @@ -612,6 +616,88 @@ 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
-> 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 : _ Thread_info.t)
~time
~(location : Event.Location.t)
=
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.
Expand Down Expand Up @@ -1120,7 +1206,10 @@ 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
Expand Down