Skip to content

Commit 1aecc99

Browse files
tkfDilumAluthgestaticfloat
authored andcommitted
CI: add the sanitizers pipelines (e.g. ASAN) to Buildkite (#41530)
* Setup CI for ASAN * Launch the `sanitizers.yml` unsigned pipeline * Use a workspace directory in ./tmp * Add some log group headers to make the logs easier to navigate * Install `julia` binary inside sandbox * Double timeout * More descriptive message from sanitizer CI * Fix the path to the binary * Use addenv * Apply suggestions from code review Co-authored-by: Elliot Saba <[email protected]> * Group ASAN related files under contrib/asan/ * Remove redundant JULIA_PRECOMPILE=1 Co-authored-by: Dilum Aluthge <[email protected]> Co-authored-by: Elliot Saba <[email protected]> (cherry picked from commit 84934e6)
1 parent 9178941 commit 1aecc99

File tree

6 files changed

+209
-1
lines changed

6 files changed

+209
-1
lines changed

.buildkite/pipeline.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,11 @@
1414
steps:
1515
- label: ":buildkite: Launch unsigned pipelines"
1616
commands: |
17+
# We launch whitespace first, because we want that pipeline to finish as quickly as possible.
18+
# The remaining unsigned pipelines are launched in alphabetical order.
1719
buildkite-agent pipeline upload .buildkite/whitespace.yml
18-
buildkite-agent pipeline upload .buildkite/llvm_passes.yml
1920
buildkite-agent pipeline upload .buildkite/embedding.yml
21+
buildkite-agent pipeline upload .buildkite/llvm_passes.yml
22+
buildkite-agent pipeline upload .buildkite/sanitizers.yml
2023
agents:
2124
queue: julia

.buildkite/sanitizers.yml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# These steps should only run on `sandbox.jl` machines, not `docker`-isolated ones
2+
# since we need nestable sandboxing. The rootfs images being used here are built from
3+
# the `.buildkite/rootfs_images/llvm-passes.jl` file.
4+
agents:
5+
queue: "julia"
6+
# Only run on `sandbox.jl` machines (not `docker`-isolated ones) since we need nestable sandboxing
7+
sandbox.jl: "true"
8+
os: "linux"
9+
10+
steps:
11+
- label: "asan"
12+
key: asan
13+
plugins:
14+
- JuliaCI/julia#v1:
15+
version: 1.6
16+
- staticfloat/sandbox#v1:
17+
rootfs_url: https://github.com/JuliaCI/rootfs-images/releases/download/v1/llvm-passes.tar.gz
18+
rootfs_treehash: "f3ed53f159e8f13edfba8b20ebdb8ece73c1b8a8"
19+
uid: 1000
20+
gid: 1000
21+
workspaces:
22+
- "/cache/repos:/cache/repos"
23+
# `contrib/check-asan.jl` needs a `julia` binary:
24+
- JuliaCI/julia#v1:
25+
version: 1.6
26+
commands: |
27+
echo "--- Build julia-debug with ASAN"
28+
contrib/asan/build.sh ./tmp/test-asan -j$${JULIA_NUM_CORES} debug
29+
echo "--- Test that ASAN is enabled"
30+
contrib/asan/check.jl ./tmp/test-asan/asan/usr/bin/julia-debug
31+
timeout_in_minutes: 120
32+
notify:
33+
- github_commit_status:
34+
context: "asan"

contrib/asan/Make.user.asan

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
TOOLCHAIN=$(BUILDROOT)/../toolchain/usr/tools
2+
3+
# use our new toolchain
4+
USECLANG=1
5+
override CC=$(TOOLCHAIN)/clang
6+
override CXX=$(TOOLCHAIN)/clang++
7+
export ASAN_SYMBOLIZER_PATH=$(TOOLCHAIN)/llvm-symbolizer
8+
9+
USE_BINARYBUILDER_LLVM=1
10+
11+
override SANITIZE=1
12+
override SANITIZE_ADDRESS=1
13+
14+
# make the GC use regular malloc/frees, which are hooked by ASAN
15+
override WITH_GC_DEBUG_ENV=1
16+
17+
# default to a debug build for better line number reporting
18+
override JULIA_BUILD_MODE=debug
19+
20+
# make ASAN consume less memory
21+
export ASAN_OPTIONS=detect_leaks=0:fast_unwind_on_malloc=0:allow_user_segv_handler=1:malloc_context_size=2
22+
23+
# tell libblastrampoline to not use RTLD_DEEPBIND
24+
export LBT_USE_RTLD_DEEPBIND=0

contrib/asan/Make.user.tools

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
USE_BINARYBUILDER_LLVM=1
2+
BUILD_LLVM_CLANG=1

contrib/asan/build.sh

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
#!/bin/bash
2+
# This file is a part of Julia. License is MIT: https://julialang.org/license
3+
#
4+
# Usage:
5+
# contrib/asan/build.sh <path> [<make_targets>...]
6+
#
7+
# Build ASAN-enabled julia. Given a workspace directory <path>, build
8+
# ASAN-enabled julia in <path>/asan. Required toolss are install under
9+
# <path>/toolchain. This scripts also takes optional <make_targets> arguments
10+
# which are passed to `make`. The default make target is `debug`.
11+
12+
set -ue
13+
14+
# `$WORKSPACE` is a directory in which we create `toolchain` and `asan`
15+
# sub-directories.
16+
WORKSPACE="$1"
17+
shift
18+
if [ "$WORKSPACE" = "" ]; then
19+
echo "Workspace directory must be specified as the first argument" >&2
20+
exit 2
21+
fi
22+
23+
mkdir -pv "$WORKSPACE"
24+
WORKSPACE="$(cd "$WORKSPACE" && pwd)"
25+
if [ "$WORKSPACE" = "" ]; then
26+
echo "Failed to create the workspace directory." >&2
27+
exit 2
28+
fi
29+
30+
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
31+
JULIA_HOME="$HERE/../../"
32+
33+
echo
34+
echo "Installing toolchain..."
35+
36+
TOOLCHAIN="$WORKSPACE/toolchain"
37+
if [ ! -d "$TOOLCHAIN" ]; then
38+
make -C "$JULIA_HOME" configure O=$TOOLCHAIN
39+
cp "$HERE/Make.user.tools" "$TOOLCHAIN/Make.user"
40+
fi
41+
42+
make -C "$TOOLCHAIN/deps" install-clang install-llvm-tools
43+
44+
echo
45+
echo "Building Julia..."
46+
47+
BUILD="$WORKSPACE/asan"
48+
if [ ! -d "$BUILD" ]; then
49+
make -C "$JULIA_HOME" configure O="$BUILD"
50+
cp "$HERE/Make.user.asan" "$BUILD/Make.user"
51+
fi
52+
53+
make -C "$BUILD" "$@"

contrib/asan/check.jl

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
#!/bin/bash
2+
# -*- mode: julia -*-
3+
# This file is a part of Julia. License is MIT: https://julialang.org/license
4+
#
5+
# Usage:
6+
# contrib/asan/check.jl <julia>
7+
#
8+
# Check that <julia> is built with ASAN.
9+
#
10+
#=
11+
JULIA="${JULIA:-julia}"
12+
exec "$JULIA" --startup-file=no --compile=min "${BASH_SOURCE[0]}" "$@"
13+
=#
14+
15+
function main(args = ARGS)::Int
16+
if length(args) != 1
17+
@error "Expect a single argument" args
18+
return 2
19+
end
20+
julia, = args
21+
22+
# It looks like double-free is easy to robustly trigger.
23+
code = """
24+
@info "Testing a pattern that would trigger ASAN"
25+
write(ARGS[1], "started")
26+
27+
ptr = ccall(:malloc, Ptr{UInt}, (Csize_t,), 256)
28+
ccall(:free, Cvoid, (Ptr{UInt},), ptr)
29+
ccall(:free, Cvoid, (Ptr{UInt},), ptr)
30+
31+
@error "Failed to trigger ASAN"
32+
"""
33+
34+
local proc
35+
timeout = Threads.Atomic{Bool}(false)
36+
isstarted = false
37+
mktemp() do tmppath, tmpio
38+
cmd = addenv(
39+
`$julia -e $code $tmppath`,
40+
"ASAN_OPTIONS" =>
41+
"detect_leaks=0:fast_unwind_on_malloc=0:allow_user_segv_handler=1:malloc_context_size=2",
42+
"LBT_USE_RTLD_DEEPBIND" => "0",
43+
)
44+
# Note: Ideally, we set ASAN_SYMBOLIZER_PATH here. But there is no easy
45+
# way to find out the path from just a Julia binary.
46+
47+
@debug "Starting a process" cmd
48+
proc = run(pipeline(cmd; stdout, stderr); wait = false)
49+
timer = Timer(10)
50+
@sync try
51+
@async begin
52+
try
53+
wait(timer)
54+
true
55+
catch err
56+
err isa EOFError || rethrow()
57+
false
58+
end && begin
59+
timeout[] = true
60+
kill(proc)
61+
end
62+
end
63+
wait(proc)
64+
finally
65+
close(timer)
66+
end
67+
68+
# At the very beginning of the process, the `julia` subprocess put a
69+
# marker that it is successfully started. This is to avoid mixing
70+
# non-functional `julia` binary (or even non-`julia` command) and
71+
# correctly working `julia` with ASAN:
72+
isstarted = read(tmpio, String) == "started"
73+
end
74+
75+
if timeout[]
76+
@error "Timeout waiting for the subprocess"
77+
return 1
78+
elseif success(proc)
79+
@error "ASAN was not triggered"
80+
return 1
81+
elseif !isstarted
82+
@error "Failed to start the process"
83+
return 1
84+
else
85+
@info "ASAN is functional in the Julia binary `$julia`"
86+
return 0
87+
end
88+
end
89+
90+
if abspath(PROGRAM_FILE) == @__FILE__
91+
exit(main())
92+
end

0 commit comments

Comments
 (0)