Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CI: add the sanitizers pipelines (e.g. ASAN) to Buildkite #41530

Merged
merged 12 commits into from
Jul 13, 2021
5 changes: 4 additions & 1 deletion .buildkite/pipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,11 @@
steps:
- label: ":buildkite: Launch unsigned pipelines"
commands: |
# We launch whitespace first, because we want that pipeline to finish as quickly as possible.
# The remaining unsigned pipelines are launched in alphabetical order.
buildkite-agent pipeline upload .buildkite/whitespace.yml
buildkite-agent pipeline upload .buildkite/llvm_passes.yml
buildkite-agent pipeline upload .buildkite/embedding.yml
buildkite-agent pipeline upload .buildkite/llvm_passes.yml
buildkite-agent pipeline upload .buildkite/sanitizers.yml
agents:
queue: julia
34 changes: 34 additions & 0 deletions .buildkite/sanitizers.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# These steps should only run on `sandbox.jl` machines, not `docker`-isolated ones
# since we need nestable sandboxing. The rootfs images being used here are built from
# the `.buildkite/rootfs_images/llvm-passes.jl` file.
agents:
queue: "julia"
# Only run on `sandbox.jl` machines (not `docker`-isolated ones) since we need nestable sandboxing
sandbox.jl: "true"
os: "linux"

steps:
- label: "asan"
key: asan
plugins:
- JuliaCI/julia#v1:
version: 1.6
tkf marked this conversation as resolved.
Show resolved Hide resolved
- staticfloat/sandbox#v1:
rootfs_url: https://github.com/JuliaCI/rootfs-images/releases/download/v1/llvm-passes.tar.gz
rootfs_treehash: "f3ed53f159e8f13edfba8b20ebdb8ece73c1b8a8"
uid: 1000
gid: 1000
workspaces:
- "/cache/repos:/cache/repos"
# `contrib/check-asan.jl` needs a `julia` binary:
- JuliaCI/julia#v1:
version: 1.6
commands: |
echo "--- Build julia-debug with ASAN"
contrib/asan/build.sh ./tmp/test-asan -j$${JULIA_NUM_CORES} debug
echo "--- Test that ASAN is enabled"
contrib/asan/check.jl ./tmp/test-asan/asan/usr/bin/julia-debug
timeout_in_minutes: 120
notify:
- github_commit_status:
context: "asan"
26 changes: 26 additions & 0 deletions contrib/asan/Make.user.asan
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
TOOLCHAIN=$(BUILDROOT)/../toolchain/usr/tools

# use our new toolchain
USECLANG=1
override CC=$(TOOLCHAIN)/clang
override CXX=$(TOOLCHAIN)/clang++
export ASAN_SYMBOLIZER_PATH=$(TOOLCHAIN)/llvm-symbolizer

USE_BINARYBUILDER_LLVM=1

override SANITIZE=1
override SANITIZE_ADDRESS=1

# make the GC use regular malloc/frees, which are hooked by ASAN
override WITH_GC_DEBUG_ENV=1

# default to a debug build for better line number reporting
override JULIA_BUILD_MODE=debug

# make ASAN consume less memory
export ASAN_OPTIONS=detect_leaks=0:fast_unwind_on_malloc=0:allow_user_segv_handler=1:malloc_context_size=2

JULIA_PRECOMPILE=1
tkf marked this conversation as resolved.
Show resolved Hide resolved

# tell libblastrampoline to not use RTLD_DEEPBIND
export LBT_USE_RTLD_DEEPBIND=0
2 changes: 2 additions & 0 deletions contrib/asan/Make.user.tools
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
USE_BINARYBUILDER_LLVM=1
BUILD_LLVM_CLANG=1
53 changes: 53 additions & 0 deletions contrib/asan/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#!/bin/bash
# This file is a part of Julia. License is MIT: https://julialang.org/license
#
# Usage:
# contrib/asan/build.sh <path> [<make_targets>...]
#
# Build ASAN-enabled julia. Given a workspace directory <path>, build
# ASAN-enabled julia in <path>/asan. Required toolss are install under
# <path>/toolchain. This scripts also takes optional <make_targets> arguments
# which are passed to `make`. The default make target is `debug`.

set -ue

# `$WORKSPACE` is a directory in which we create `toolchain` and `asan`
# sub-directories.
WORKSPACE="$1"
shift
if [ "$WORKSPACE" = "" ]; then
echo "Workspace directory must be specified as the first argument" >&2
exit 2
fi

mkdir -pv "$WORKSPACE"
WORKSPACE="$(cd "$WORKSPACE" && pwd)"
if [ "$WORKSPACE" = "" ]; then
echo "Failed to create the workspace directory." >&2
exit 2
fi

HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
JULIA_HOME="$HERE/../../"

echo
echo "Installing toolchain..."

TOOLCHAIN="$WORKSPACE/toolchain"
if [ ! -d "$TOOLCHAIN" ]; then
make -C "$JULIA_HOME" configure O=$TOOLCHAIN
cp "$HERE/Make.user.tools" "$TOOLCHAIN/Make.user"
fi

make -C "$TOOLCHAIN/deps" install-clang install-llvm-tools

echo
echo "Building Julia..."

BUILD="$WORKSPACE/asan"
if [ ! -d "$BUILD" ]; then
make -C "$JULIA_HOME" configure O="$BUILD"
cp "$HERE/Make.user.asan" "$BUILD/Make.user"
fi

make -C "$BUILD" "$@"
92 changes: 92 additions & 0 deletions contrib/asan/check.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
#!/bin/bash
# -*- mode: julia -*-
# This file is a part of Julia. License is MIT: https://julialang.org/license
#
# Usage:
# contrib/asan/check.jl <julia>
#
# Check that <julia> is built with ASAN.
#
#=
JULIA="${JULIA:-julia}"
exec "$JULIA" --startup-file=no --compile=min "${BASH_SOURCE[0]}" "$@"
=#

function main(args = ARGS)::Int
if length(args) != 1
@error "Expect a single argument" args
return 2
end
julia, = args

# It looks like double-free is easy to robustly trigger.
code = """
@info "Testing a pattern that would trigger ASAN"
write(ARGS[1], "started")

ptr = ccall(:malloc, Ptr{UInt}, (Csize_t,), 256)
ccall(:free, Cvoid, (Ptr{UInt},), ptr)
ccall(:free, Cvoid, (Ptr{UInt},), ptr)

@error "Failed to trigger ASAN"
"""

local proc
timeout = Threads.Atomic{Bool}(false)
isstarted = false
mktemp() do tmppath, tmpio
cmd = addenv(
`$julia -e $code $tmppath`,
"ASAN_OPTIONS" =>
"detect_leaks=0:fast_unwind_on_malloc=0:allow_user_segv_handler=1:malloc_context_size=2",
"LBT_USE_RTLD_DEEPBIND" => "0",
)
# Note: Ideally, we set ASAN_SYMBOLIZER_PATH here. But there is no easy
# way to find out the path from just a Julia binary.

@debug "Starting a process" cmd
proc = run(pipeline(cmd; stdout, stderr); wait = false)
timer = Timer(10)
@sync try
@async begin
try
wait(timer)
true
catch err
err isa EOFError || rethrow()
false
end && begin
timeout[] = true
kill(proc)
end
end
wait(proc)
finally
close(timer)
end

# At the very beginning of the process, the `julia` subprocess put a
# marker that it is successfully started. This is to avoid mixing
# non-functional `julia` binary (or even non-`julia` command) and
# correctly working `julia` with ASAN:
isstarted = read(tmpio, String) == "started"
end

if timeout[]
@error "Timeout waiting for the subprocess"
return 1
elseif success(proc)
@error "ASAN was not triggered"
return 1
elseif !isstarted
@error "Failed to start the process"
return 1
else
@info "ASAN is functional in the Julia binary `$julia`"
return 0
end
end

if abspath(PROGRAM_FILE) == @__FILE__
exit(main())
end