Skip to content

Commit 8cee0b2

Browse files
KJTsanaktsidiseregon
authored andcommitted
Add ASAN builds
It compiles some of Ruby's key dependencies with ASAN enabled, ships those dynamic libraries to a location in Ruby's prefix (which is part of the tarball deployed with setup-ruby). -Wl,-rpath is used to make sure that these ASANified libs are used in preference to any distribution-provided libs at runtime. Only Ubuntu 24.04 is supported, because the version of clang in 22.04 is too old. MacOS is also not yet supported.
1 parent c3908d8 commit 8cee0b2

File tree

2 files changed

+115
-0
lines changed

2 files changed

+115
-0
lines changed

.github/workflows/build.yml

+36
Original file line numberDiff line numberDiff line change
@@ -80,13 +80,19 @@ jobs:
8080
matrix:
8181
os: [ ubuntu-20.04, ubuntu-22.04, ubuntu-24.04, macos-11, macos-arm-oss ]
8282
name: [ head, debug ]
83+
include:
84+
- { os: ubuntu-24.04, name: asan }
8385
runs-on: ${{ matrix.os }}
8486
steps:
8587
- name: Clone ruby
8688
uses: actions/checkout@v4
8789
with:
8890
repository: ruby/ruby
8991
ref: ${{ needs.prepare.outputs.commit }}
92+
- name: Clone ruby-dev-builder
93+
uses: actions/checkout@v4
94+
with:
95+
path: ruby-dev-builder
9096

9197
- name: Set platform
9298
id: platform
@@ -128,6 +134,36 @@ jobs:
128134
echo "cppflags=-DENABLE_PATH_CHECK=0 -DRUBY_DEBUG=1" >> $GITHUB_ENV
129135
echo "optflags=-O3 -fno-inline" >> $GITHUB_ENV
130136
if: matrix.name == 'debug'
137+
- name: Build dependencies and set configure flags (asan)
138+
run: |
139+
set -ex
140+
# We only test ASAN with Clang. The version of Clang needs to be > 18, which the version in
141+
# Ubuntu 24.04's repo is
142+
sudo apt-get install -y clang
143+
144+
# ASAN builds need to compile (some of) their own dependencies with ASAN enabled, so that it can
145+
# catch memory errors caused by passing invalid parameters into other libraries.
146+
ASAN_LIB_PREFIX="$HOME/.rubies/ruby-${{ matrix.name }}"
147+
./ruby-dev-builder/asan_libs.rb \
148+
--prefix="$ASAN_LIB_PREFIX" \
149+
--cc=clang \
150+
--cflags="-fsanitize=address -fno-omit-frame-pointer -ggdb3 -O3" \
151+
--ldflags="-Wl,-rpath=$ASAN_LIB_PREFIX/lib" \
152+
--makeopts="-j4"
153+
154+
# Set Ruby configure flags
155+
# Clang > 17 does not work with M:N threading, so we disable it: https://bugs.ruby-lang.org/issues/20243
156+
echo "cppflags=-DENABLE_PATH_CHECK=0 -DRUBY_DEBUG=1 -DVM_CHECK_MODE=1 -DUSE_MN_THREADS=0" >> $GITHUB_ENV
157+
echo "optflags=-O3 -fno-omit-frame-pointer" >> $GITHUB_ENV
158+
echo "debugflags=-fsanitize=address -ggdb3" >> $GITHUB_ENV
159+
echo "CC=clang" >> $GITHUB_ENV
160+
# Make sure we link against the ASAN libs we built
161+
echo "cflags=-I$ASAN_LIB_PREFIX/include" >> $GITHUB_ENV
162+
echo "LDFLAGS=-L$ASAN_LIB_PREFIX/lib -Wl,-rpath=$ASAN_LIB_PREFIX/lib" >> $GITHUB_ENV
163+
# Make the test timeouts more generous too (ASAN is slower)
164+
echo "RUBY_TEST_TIMEOUT_SCALE=5" >> $GITHUB_ENV
165+
echo "SYNTAX_SUGGEST_TIMEOUT=600" >> $GITHUB_ENV
166+
if: matrix.name == 'asan'
131167

132168
# Build
133169
- run: chmod 755 $HOME # https://github.com/actions/virtual-environments/issues/267

asan_libs.rb

+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
#!/usr/bin/env ruby
2+
3+
require 'optparse'
4+
require 'fileutils'
5+
require 'tmpdir'
6+
require 'shellwords'
7+
8+
$prefix = $cflags = $ldflags = $cc = $makeopts = nil
9+
OptionParser.new do |opts|
10+
opts.banner = "Usage: asan_libs.rb --prefix=$RUBY_PREFIX"
11+
opts.on('--prefix=PREFIX') { $prefix = _1 }
12+
opts.on('--cflags=CFLAGS') { $cflags = _1 }
13+
opts.on('--ldflags=LDFLAGS') { $ldflags= _1 }
14+
opts.on('--cc=CC') { $cc = _1 }
15+
opts.on('--makeopts=MAKEOPTS') { $makeopts = _1 }
16+
end.parse!
17+
raise "--prefix must be specified" if $prefix.nil?
18+
19+
20+
OPENSSL_VERSION = "3.3.0"
21+
LIBYAML_VERSION = "0.2.5"
22+
23+
$cflags_args = []
24+
$cflags_args << "CC=#{$cc}" if $cc
25+
$cflags_args << "CFLAGS=#{$cflags}" if $cflags
26+
$cflags_args << "LDFLAGS=#{$ldflags}" if $ldflags
27+
$makeopts = Shellwords.split($makeopts || '')
28+
29+
def sh!(*args, **kwargs)
30+
puts "==> #{Shellwords.join(args)}"
31+
system(*args, **kwargs, exception: true)
32+
end
33+
34+
def chdir!(dir, &block)
35+
puts "==> cd #{File.realpath dir}"
36+
Dir.chdir dir, &block
37+
end
38+
39+
def rmglob!(what)
40+
Dir.glob(what).each do |file|
41+
puts "==> rm #{file}"
42+
FileUtils.rm_f file
43+
end
44+
end
45+
46+
def compile_openssl
47+
# Make sure we compile OpenSSL to look for certificates in the same place that the
48+
# distribution provided OpenSSl would.
49+
openssldir = Shellwords.split(`openssl version -d`.match(/^\s*OPENSSLDIR:(.*)$/)[1].strip).first
50+
51+
sh! *%w[curl -fsSLO], "https://www.openssl.org/source/openssl-#{OPENSSL_VERSION}.tar.gz"
52+
sh! *%w[tar -xf], "openssl-#{OPENSSL_VERSION}.tar.gz"
53+
chdir!("openssl-#{OPENSSL_VERSION}") do
54+
sh! './Configure', "--prefix=#{$prefix}", '--libdir=lib', "--openssldir=#{openssldir}",
55+
*%w[shared no-tests no-apps], *$cflags_args
56+
sh! 'make', *$makeopts
57+
# make install_sw will try and write config to OPENSSLDIR, which we don't want to do.
58+
sh! *%w[make install_dev], exception: true
59+
# OpenSSL make install_dev will also install static libraries, which we don't need
60+
rmglob! File.join($prefix, "lib/*.a")
61+
end
62+
end
63+
64+
def compile_libyaml
65+
sh! *%w[curl -fsSLO], "http://pyyaml.org/download/libyaml/yaml-#{LIBYAML_VERSION}.tar.gz"
66+
sh! *%w[tar -xf], "yaml-#{LIBYAML_VERSION}.tar.gz"
67+
chdir!("yaml-#{LIBYAML_VERSION}") do
68+
sh! './configure', "--prefix=#{$prefix}", *%w[--disable-static --enable-shared], *$cflags_args
69+
sh! 'make', *$makeopts
70+
sh! 'make', 'install'
71+
end
72+
end
73+
74+
Dir.mktmpdir('asan_libs') do |build_dir|
75+
Dir.chdir(build_dir) do
76+
compile_openssl
77+
compile_libyaml
78+
end
79+
end

0 commit comments

Comments
 (0)