diff options
33 files changed, 761 insertions, 105 deletions
diff --git a/HOWTO/INSTALL.md b/HOWTO/INSTALL.md index b3d30761e2..3bf6be8c89 100644 --- a/HOWTO/INSTALL.md +++ b/HOWTO/INSTALL.md @@ -604,7 +604,7 @@ using the similar steps just described. $ (cd $ERL_TOP/erts/emulator && make $TYPE) -where `$TYPE` is `opt`, `gcov`, `gprof`, `debug`, `valgrind`, or `lcnt`. +where `$TYPE` is `opt`, `gcov`, `gprof`, `debug`, `valgrind`, `asan` or `lcnt`. These different beam types are useful for debugging and profiling purposes. diff --git a/HOWTO/TESTING.md b/HOWTO/TESTING.md index 020be0309c..7a7f6982f2 100644 --- a/HOWTO/TESTING.md +++ b/HOWTO/TESTING.md @@ -185,6 +185,52 @@ examine the results so far for the currently executing test suite (in R14B02 and later you want to open the `release/tests/test_server/all_runs.html` file to get to the currently running test) + +Run tests with Address Sanitizer +-------------------------------- + +First build emulator with `asan` build target. +See [$ERL_TOP/HOWTO/INSTALL.md][]. + +Set environment variable `ASAN_LOG_DIR` to the directory +where the error logs will be generated. + + export ASAN_LOG_DIR=$TESTROOT/test_server/asan_logs + mkdir $ASAN_LOG_DIR + +Set environment variable `TS_RUN_EMU` to `asan`. + + export TS_RUN_EMU=asan + +Then run the tests you want with `ts:run` as described above. Either +inspect the log files directly or use the script at +`$ERL_TOP/erts/emulator/asan/asan_logs_to_html` to read all log files +in `$ASAN_LOG_DIR` and distill them into one html page +`asan_summary.html`. Repeated reports from the same memory leak will +for example be ignored by the script and make it easier to analyze. + + +Run tests with Valgrind +----------------------- + +First make sure [valgrind][] is installed, then build OTP from source +and build the emulator with `valgrind` build target. See +[$ERL_TOP/HOWTO/INSTALL.md][]. + +Set environment variable `VALGRIND_LOG_DIR` to the directory +where the valgrind error logs will be generated. + + export VALGRIND_LOG_DIR=$TESTROOT/test_server/vg_logs + mkdir $VALGRIND_LOG_DIR + +Set environment variable `TS_RUN_EMU` to `valgrind`. + + export TS_RUN_EMU=valgrind + +Then run the tests you want with `ts:run` as described above and +inspect the log file(s) in `$VALGRIND_LOG_DIR`. + + [ct_run]: http://www.erlang.org/doc/man/ct_run.html [ct hook]: http://www.erlang.org/doc/apps/common_test/ct_hooks_chapter.html [$ERL_TOP/HOWTO/INSTALL.md]: INSTALL.md @@ -192,5 +238,6 @@ get to the currently running test) [common_test]: http://www.erlang.org/doc/man/ct.html [data_dir]: http://www.erlang.org/doc/apps/common_test/write_test_chapter.html#data_priv_dir [configuring the tests]: #configuring-the-test-environment + [valgrind]: https://valgrind.org [?TOC]: true diff --git a/erts/emulator/Makefile.in b/erts/emulator/Makefile.in index 794fed41cf..fcc302e4c8 100644 --- a/erts/emulator/Makefile.in +++ b/erts/emulator/Makefile.in @@ -144,6 +144,14 @@ TYPE_FLAGS = $(DEBUG_CFLAGS) -DVALGRIND -DNO_JUMP_TABLE ENABLE_ALLOC_TYPE_VARS += valgrind else +ifeq ($(TYPE),asan) +PURIFY = +TYPEMARKER = .asan +TYPE_FLAGS = $(DEBUG_CFLAGS) -fsanitize=address -fsanitize-recover=address -fno-omit-frame-pointer -DADDRESS_SANITIZER +LDFLAGS += -fsanitize=address +ENABLE_ALLOC_TYPE_VARS += asan +else + ifeq ($(TYPE),gprof) TYPEMARKER = .gprof TYPE_FLAGS = @CFLAGS@ -DGPROF -pg -DERTS_CAN_INLINE=0 -DERTS_INLINE= @@ -180,6 +188,7 @@ endif endif endif endif +endif LIBS += $(TYPE_LIBS) diff --git a/erts/emulator/asan/asan_logs_to_html b/erts/emulator/asan/asan_logs_to_html new file mode 100755 index 0000000000..9e20d4051b --- /dev/null +++ b/erts/emulator/asan/asan_logs_to_html @@ -0,0 +1,234 @@ +#!/usr/bin/env escript +%% -*- erlang -*- + +%% Parse address sanitizer log files generated from test runs with +%% with environment variables ASAN_LOG_DIR and TS_RUN_EMU=asan set. + +%% Repeated leak reports are ignored and additional leaks of same type +%% as seen before are identified as such. + +-mode(compile). + +main([]) -> + help(); +main(["--help"]) -> + help(); +main([OutDir]) -> + case os:getenv("ASAN_LOG_DIR") of + false -> + io:format(standard_error, + "\nMissing asan log directory argument and environment\n" + "variable ASAN_LOG_DIR is not set.\n\n",[]), + help(); + InDir -> + run(OutDir, InDir) + end; +main([OutDir, InDir]) -> + run(OutDir, InDir). + + +help() -> + io:format("\nSyntax: asan_log_to_html OutDir [InDir]\n" + "\nParses all address-sanetizer log files in InDir\n" + "and generates a summary file OutDir/asan_summary.html.\n" + "Environment variable ASAN_LOG_DIR is used if InDir\n" + "is not specified\n\n", []). + +run(OutDir, InDir) -> + {ok, InFilesUS} = file:list_dir(InDir), + InFiles = lists:sort(InFilesUS), + + OutFile = filename:join(OutDir, "asan_summary.html"), + {ok, FD} = file:open(OutFile, [write]), + + ok = file:write(FD, <<"<!DOCTYPE html>\n" + "<html>\n" + "<head><title>Address Sanitizer</title></head>\n" + "<body>\n" + "<h1>Address Sanitizer</h1>\n">>), + + lists:foldl(fun(File, Acc) -> + io:format("analyze ~s\n", [File]), + analyze_log_file(filename:join(InDir,File), + FD, Acc) + end, + {#{}, none, none}, + InFiles), + + ok = io:format(FD, "<hr>\n", []), + + Time = calendar:system_time_to_rfc3339(erlang:system_time(second), + [{time_designator, 32}]), + %%{_, _, ThisFile} = code:get_object_code(?MODULE), + ThisFile = escript:script_name(), + User = string:trim(os:cmd("whoami")), + {ok, Host} = inet:gethostname(), + ok = io:format(FD, "<p><small>This page was generated ~s\n" + " by <tt>~s</tt>\n" + " run by ~s@~s.</small></p>\n", + [Time, ThisFile, User, Host]), + + ok = file:write(FD, <<"</body>\n</html>\n">>), + ok = file:close(FD), + io:format("Generated file ~s\n", [OutFile]), + ok. + +analyze_log_file(SrcFile, OutFD, {LeakMap0, PrevApp, RegEx0}) -> + + [_Exe, App | _] = string:lexemes(filename:basename(SrcFile), "-"), + case App of + PrevApp -> ignore; + _ -> + Line = case PrevApp of + none -> ""; + _ -> "<hr>" + end, + ok = io:format(OutFD, "~s<h2>~s</h2>\n", [Line, App]) + end, + + {ok, Bin} = file:read_file(SrcFile), + + {Leaks, RegEx1} = + run_regex(Bin, RegEx0, + %% LeakReport + "(?:(Direct|Indirect) leak of ([0-9]+) byte\\(s\\) " + "in ([0-9]+) object\\(s\\) allocated from:\n" + "((?:[ \t]*#[0-9]+.+\n)+))" % Call stack + "|" + %% ErrorReport + "(?:(==ERROR: AddressSanitizer:.*\n" + "(?:.*\n)+?)" % any lines (non-greedy) + "^(?:==|--))" % stop at line begining with == or -- + "|" + %% Skipped + "(?:^[=-]+$)" % skip lines consisting only of = or - + "|" + "Objects leaked above:\n" % if LSAN_OPTIONS="report_objects=1" + "(?:0x.+\n)+" + "|" + "^\n", % empty lines + [multiline], + [global, {capture, all, index}]), + + %% We indentify a leak by its type (direct or indirect) + %% and its full call stack. + + BP = fun(PartIx) -> binary:part(Bin, PartIx) end, + + LeakChecker = + fun([ErrorReport, {-1,0}, {-1,0}, {-1,0}, {-1,0}, Captured], + {Out, PrevEnd, Unmatched0, LM0}) -> + {Start,MatchLen} = ErrorReport, + FD = fd(Out), + ok = io:format(FD, "<p><pre~s>\n", [style(error)]), + ok = file:write(FD, BP(Captured)), + ok = io:format(FD, "</pre></p>\n", []), + Unmatched1 = [BP({PrevEnd, Start-PrevEnd}) | Unmatched0], + End = Start + MatchLen, + {FD, End, Unmatched1, LM0}; + + ([LeakReport, TypeIx, BytesIx, BlocksIx, StackIx | _], + {Out, PrevEnd, Unmatched0, LM0}) -> + {Start, MatchLen} = LeakReport, + Bytes = binary_to_integer(BP(BytesIx)), + Blocks = binary_to_integer(BP(BlocksIx)), + End = Start + MatchLen, + Unmatched1 = [BP({PrevEnd, Start-PrevEnd})|Unmatched0], + TypeBin = BP(TypeIx), + Key = {TypeBin, BP(StackIx)}, + case lookup_leak(LM0, Key) of + undefined -> + %% A new leak + LM1 = insert_leak(LM0, Key, Bytes, Blocks), + FD = fd(Out), + ok = io:format(FD, "<p><pre~s>\n", [style(new, TypeBin)]), + ok = file:write(FD, BP(LeakReport)), + ok = io:format(FD, "</pre></p>\n", []), + {FD, End, Unmatched1, LM1}; + + {Bytes, Blocks} -> + %% Exact same leak(s) repeated, ignore + {Out, End, Unmatched1, LM0}; + + {OldBytes, OldBlocks} -> + %% More leaked bytes/blocks of same type&stack as before + LM1 = insert_leak(LM0, Key, Bytes, Blocks), + FD = fd(Out), + ok = io:format(FD, "<p><pre~s>\n", [style(more, TypeBin)]), + ok = io:format(FD, "More ~s leak of ~w(~w) byte(s) " + "in ~w(~w) object(s) allocated from:\n", + [TypeBin, Bytes - OldBytes, Bytes, + Blocks - OldBlocks, Blocks]), + ok = file:write(FD, BP(StackIx)), + ok = io:format(FD, "</pre></p>\n", []), + {FD, End, Unmatched1, LM1} + end; + ([SkipLine], {Out, PrevEnd, Unmatched0, LM0}) -> + {Start, MatchLen} = SkipLine, + %%nomatch = binary:match(BP(SkipLine), <<"\n">>), % Assert single line + End = Start + MatchLen, + Unmatched1 = [BP({PrevEnd, Start-PrevEnd})|Unmatched0], + {Out, End, Unmatched1, LM0} + end, + Out0 = {OutFD, SrcFile}, + {Out1, LastEnd, Unmatched1, LeakMap1} = lists:foldl(LeakChecker, + {Out0, 0, [], LeakMap0}, + Leaks), + + Unmatched2 = [BP({LastEnd, byte_size(Bin)-LastEnd}) | Unmatched1], + + case iolist_size(Unmatched2) > 500 of + true -> + FD = fd(Out1), + ok = io:format(FD, "<h2>WARNING!!! May be unmatched error reports" + " in file ~s:</h2>\n<p><pre>~s</pre></p>", [SrcFile, Unmatched2]), + FD; + false -> + Out1 + end, + {LeakMap1, App, RegEx1}. + +lookup_leak(LeakMap, Key) -> + maps:get(Key, LeakMap, undefined). + +insert_leak(LeakMap, Key, Bytes, Blocks) -> + LeakMap#{Key => {Bytes, Blocks}}. + +fd({FD, SrcFile}) -> + TcFile = filename:basename(SrcFile), + case string:lexemes(TcFile, "-") of + [_Exe, App, _Rest] -> + ok = io:format(FD, "<h3>Before first test case of ~s</h3>\n", + [App]); + [_Exe, _App, "tc", Num, Mod, Rest] -> + [Func | _] = string:lexemes(Rest, "."), + ok = io:format(FD, "<h3>Test case #~s ~s:~s</h3>\n", [Num, Mod, Func]); + _ -> + ok = io:format(FD, "<h3>Strange log file name '~s'</h3>\n", + [SrcFile]) + end, + FD; +fd(FD) -> + FD. + +style(error) -> + " style=\"background-color:Tomato;\"". + +style(new, <<"Direct">>) -> + " style=\"background-color:orange;\""; +style(new, <<"Indirect">>) -> + ""; +style(more, _) -> + " style=\"background-color:yellow;\"". + + +run_regex(Bin, none, RegExString, CompileOpts, RunOpts) -> + {ok, RegEx} = re:compile(RegExString, CompileOpts), + run_regex(Bin, RegEx, none, none, RunOpts); +run_regex(Bin, RegEx, _, _, RunOpts) -> + case re:run(Bin, RegEx, RunOpts) of + nomatch -> + {[], RegEx}; + {match, List} -> + {List, RegEx} + end. diff --git a/erts/emulator/asan/suppress b/erts/emulator/asan/suppress new file mode 100644 index 0000000000..5625938f37 --- /dev/null +++ b/erts/emulator/asan/suppress @@ -0,0 +1,18 @@ +leak:erts_alloc_permanent_cache_aligned + +# Harmless leak of ErtsThrPrgrData from async threads in exiting emulator +leak:erts_thr_progress_register_unmanaged_thread + +# Block passed to sigaltstack() +leak:sys_thread_init_signal_stack + +#Copied from valgrind/suppress.standard: +#Crypto internal... loading gives expected errors when curves are tried. +#But including <openssl/err.h> and removing them triggers compiler errors on Windows +#fun:valid_curve +#fun:init_curves +leak:init_curve_types +#fun:init_algorithms_types +#fun:initialize +#fun:load +#fun:erts_load_nif diff --git a/erts/emulator/beam/erl_alloc.c b/erts/emulator/beam/erl_alloc.c index 8d739ba654..72497ef6a1 100644 --- a/erts/emulator/beam/erl_alloc.c +++ b/erts/emulator/beam/erl_alloc.c @@ -66,7 +66,7 @@ #define ERTS_ALC_DEFAULT_MAX_THR_PREF ERTS_MAX_NO_OF_SCHEDULERS -#if defined(SMALL_MEMORY) || defined(VALGRIND) +#if defined(SMALL_MEMORY) || defined(VALGRIND) || defined(ADDRESS_SANITIZER) #define AU_ALLOC_DEFAULT_ENABLE(X) 0 #else #define AU_ALLOC_DEFAULT_ENABLE(X) (X) @@ -284,7 +284,11 @@ static void set_default_literal_alloc_opts(struct au_init *ip) { SET_DEFAULT_ALLOC_OPTS(ip); +#ifdef ADDRESS_SANITIZER + ip->enable = 0; +#else ip->enable = 1; +#endif ip->thr_spec = 0; ip->disable_allowed = 0; ip->thr_spec_allowed = 0; diff --git a/erts/emulator/beam/erl_alloc.h b/erts/emulator/beam/erl_alloc.h index c13cf3f5b0..831e7ab0a7 100644 --- a/erts/emulator/beam/erl_alloc.h +++ b/erts/emulator/beam/erl_alloc.h @@ -358,24 +358,11 @@ erts_alloc_get_verify_unused_temp_alloc(Allctr_t **allctr); #define ERTS_ALC_CACHE_LINE_ALIGN_SIZE(SZ) \ (((((SZ) - 1) / ERTS_CACHE_LINE_SIZE) + 1) * ERTS_CACHE_LINE_SIZE) +#if !defined(VALGRIND) && !defined(ADDRESS_SANITIZER) + #define ERTS_QUALLOC_IMPL(NAME, TYPE, PASZ, ALCT) \ ERTS_QUICK_ALLOC_IMPL(NAME, TYPE, PASZ, ALCT, (void) 0, (void) 0, (void) 0) -#define ERTS_TS_QUALLOC_IMPL(NAME, TYPE, PASZ, ALCT) \ -ERTS_QUALLOC_IMPL(NAME, TYPE, PASZ, ALCT) - -#define ERTS_TS_PALLOC_IMPL(NAME, TYPE, PASZ) \ -static erts_spinlock_t NAME##_lck; \ -ERTS_PRE_ALLOC_IMPL(NAME, TYPE, PASZ, \ - erts_spinlock_init(&NAME##_lck, #NAME "_alloc_lock", NIL, \ - ERTS_LOCK_FLAGS_CATEGORY_ALLOCATOR),\ - erts_spin_lock(&NAME##_lck), \ - erts_spin_unlock(&NAME##_lck)) - - -#define ERTS_PALLOC_IMPL(NAME, TYPE, PASZ) \ - ERTS_TS_PALLOC_IMPL(NAME, TYPE, PASZ) - #define ERTS_QUICK_ALLOC_IMPL(NAME, TYPE, PASZ, ALCT, ILCK, LCK, ULCK) \ ERTS_PRE_ALLOC_IMPL(NAME##_pre, TYPE, PASZ, ILCK, LCK, ULCK) \ @@ -606,6 +593,69 @@ NAME##_free(TYPE *p) \ (char *) p); \ } +#else /* !defined(VALGRIND) && !defined(ADDRESS_SANITIZER) */ + +/* + * For VALGRIND and ADDRESS_SANITIZER we short circuit all preallocation + * with dummy wrappers around malloc and free. + */ + +#define ERTS_QUALLOC_IMPL(NAME, TYPE, PASZ, ALCT) \ + ERTS_QUICK_ALLOC_IMPL(NAME, TYPE, PASZ, ALCT, (void) 0, (void) 0, (void) 0) + +#define ERTS_QUICK_ALLOC_IMPL(NAME, TYPE, PASZ, ALCT, ILCK, LCK, ULCK) \ +static void init_##NAME##_alloc(void) \ +{ \ +} \ +static ERTS_INLINE TYPE* NAME##_alloc(void) \ +{ \ + return malloc(sizeof(TYPE)); \ +} \ +static ERTS_INLINE void NAME##_free(TYPE *p) \ +{ \ + free((void *) p); \ +} + +#define ERTS_SCHED_PREF_PALLOC_IMPL(NAME, TYPE, PASZ) \ + ERTS_SCHED_PREF_PRE_ALLOC_IMPL(NAME, TYPE, PASZ) + +#define ERTS_SCHED_PREF_AUX(NAME, TYPE, PASZ) \ +ERTS_SCHED_PREF_PRE_ALLOC_IMPL(NAME##_pre, TYPE, PASZ) + +#define ERTS_SCHED_PREF_QUICK_ALLOC_IMPL(NAME, TYPE, PASZ, ALCT) \ + ERTS_QUALLOC_IMPL(NAME, TYPE, PASZ, ALCT) + +#define ERTS_THR_PREF_QUICK_ALLOC_IMPL(NAME, TYPE, PASZ, ALCT) \ +void erts_##NAME##_pre_alloc_init_thread(void) \ +{ \ +} \ +static void init_##NAME##_alloc(int nthreads) \ +{ \ +} \ +static ERTS_INLINE TYPE* NAME##_alloc(void) \ +{ \ + return malloc(sizeof(TYPE)); \ +} \ +static ERTS_INLINE void NAME##_free(TYPE *p) \ +{ \ + free(p); \ +} + +#define ERTS_SCHED_PREF_PRE_ALLOC_IMPL(NAME, TYPE, PASZ) \ +static void init_##NAME##_alloc(void) \ +{ \ +} \ +static TYPE* NAME##_alloc(void) \ +{ \ + return (TYPE *) malloc(sizeof(TYPE)); \ +} \ +static int NAME##_free(TYPE *p) \ +{ \ + free(p); \ + return 1; \ +} + +#endif /* VALGRIND || ADDRESS_SANITIZER */ #ifdef DEBUG #define ERTS_ALC_DBG_BLK_SZ(PTR) (*(((UWord *) (PTR)) - 2)) diff --git a/erts/emulator/beam/erl_bif_info.c b/erts/emulator/beam/erl_bif_info.c index 5fa85683d8..66825f3d2e 100644 --- a/erts/emulator/beam/erl_bif_info.c +++ b/erts/emulator/beam/erl_bif_info.c @@ -59,8 +59,11 @@ #endif #ifdef VALGRIND -#include <valgrind/valgrind.h> -#include <valgrind/memcheck.h> +# include <valgrind/valgrind.h> +# include <valgrind/memcheck.h> +#endif +#ifdef ADDRESS_SANITIZER +# include <sanitizer/lsan_interface.h> #endif static Export* alloc_info_trap = NULL; @@ -125,6 +128,9 @@ static char erts_system_version[] = ("Erlang/OTP " ERLANG_OTP_RELEASE #ifdef VALGRIND " [valgrind-compiled]" #endif +#ifdef ADDRESS_SANITIZER + " [address-sanitizer]" +#endif #ifdef ERTS_FRMPTR " [frame-pointer]" #endif @@ -2119,6 +2125,28 @@ current_stacktrace(Process *p, ErtsHeapFactory *hfact, Process* rp, return res; } +#if defined(VALGRIND) || defined(ADDRESS_SANITIZER) +static int iolist_to_tmp_buf(Eterm iolist, char** bufp) +{ + ErlDrvSizeT buf_size = 1024; /* Try with 1KB first */ + char *buf = erts_alloc(ERTS_ALC_T_TMP, buf_size); + ErlDrvSizeT r = erts_iolist_to_buf(iolist, (char*) buf, buf_size - 1); + if (ERTS_IOLIST_TO_BUF_FAILED(r)) { + erts_free(ERTS_ALC_T_TMP, (void *) buf); + if (erts_iolist_size(iolist, &buf_size)) { + return 0; + } + buf_size++; + buf = erts_alloc(ERTS_ALC_T_TMP, buf_size); + r = erts_iolist_to_buf(iolist, (char*) buf, buf_size - 1); + ASSERT(r == buf_size - 1); + } + buf[buf_size - 1 - r] = '\0'; + *bufp = buf; + return 1; +} +#endif + /* * This function takes care of calls to erlang:system_info/1 when the argument * is a tuple. @@ -2181,40 +2209,72 @@ info_1_tuple(Process* BIF_P, /* Pointer to current process. */ goto badarg; ERTS_BIF_PREP_TRAP1(ret, erts_format_cpu_topology_trap, BIF_P, res); return ret; -#if defined(VALGRIND) - } else if (ERTS_IS_ATOM_STR("error_checker", sel) - || ERTS_IS_ATOM_STR("valgrind", sel)) { - if (*tp == am_memory) { -# ifdef VALGRIND_DO_ADDED_LEAK_CHECK + } else if (ERTS_IS_ATOM_STR("memory_checker", sel)) { + if (arity == 2 && ERTS_IS_ATOM_STR("test_leak", *tp)) { +#if defined(VALGRIND) || defined(ADDRESS_SANITIZER) + erts_alloc(ERTS_ALC_T_HEAP , 100); +#endif + BIF_RET(am_ok); + } + else if (arity == 2 && ERTS_IS_ATOM_STR("test_overflow", *tp)) { + static int test[2]; + BIF_RET(make_small(test[2])); + } +#if defined(VALGRIND) || defined(ADDRESS_SANITIZER) + if (arity == 2 && *tp == am_running) { +# if defined(VALGRIND) + if (RUNNING_ON_VALGRIND) + BIF_RET(ERTS_MAKE_AM("valgrind")); +# elif defined(ADDRESS_SANITIZER) + BIF_RET(ERTS_MAKE_AM("asan")); +# endif + } + else if (arity == 2 && ERTS_IS_ATOM_STR("check_leaks", *tp)) { +# if defined(VALGRIND) +# ifdef VALGRIND_DO_ADDED_LEAK_CHECK VALGRIND_DO_ADDED_LEAK_CHECK; -# else +# else VALGRIND_DO_LEAK_CHECK; +# endif + BIF_RET(am_ok); +# elif defined(ADDRESS_SANITIZER) + __lsan_do_recoverable_leak_check(); + BIF_RET(am_ok); # endif - BIF_RET(make_small(0)); - } else if (*tp == am_fd) { - /* Not present in valgrind... */ - BIF_RET(make_small(0)); - } else if (*tp == am_running) { - BIF_RET(RUNNING_ON_VALGRIND ? am_true : am_false); - } else if (is_list(*tp)) { - ErlDrvSizeT buf_size = 8*1024; /* Try with 8KB first */ - char *buf = erts_alloc(ERTS_ALC_T_TMP, buf_size); - ErlDrvSizeT r = erts_iolist_to_buf(*tp, (char*) buf, buf_size - 1); - if (ERTS_IOLIST_TO_BUF_FAILED(r)) { - erts_free(ERTS_ALC_T_TMP, (void *) buf); - if (erts_iolist_size(*tp, &buf_size)) { - goto badarg; - } - buf_size++; - buf = erts_alloc(ERTS_ALC_T_TMP, buf_size); - r = erts_iolist_to_buf(*tp, (char*) buf, buf_size - 1); - ASSERT(r == buf_size - 1); - } - buf[buf_size - 1 - r] = '\0'; + } +# if defined(VALGRIND) + if (arity == 3 && tp[0] == am_print && is_list(tp[1])) { + char* buf; + if (!iolist_to_tmp_buf(tp[1], &buf)) + goto badarg; VALGRIND_PRINTF("%s\n", buf); erts_free(ERTS_ALC_T_TMP, (void *) buf); BIF_RET(am_true); } +# endif +# if defined(ADDRESS_SANITIZER) + if (arity == 3 && ERTS_IS_ATOM_STR("log",tp[0]) && is_list(tp[1])) { + static char *active_log = NULL; + static int active_log_len; + Eterm ret = NIL; + char* buf; + if (!iolist_to_tmp_buf(tp[1], &buf)) + goto badarg; + erts_rwmtx_rwlock(&erts_dist_table_rwmtx); /* random lock abuse */ + __sanitizer_set_report_path(buf); + if (active_log) { + Eterm *hp = HAlloc(BIF_P, 2 * active_log_len); + ret = erts_bld_string_n(&hp, 0, active_log, active_log_len); + erts_free(ERTS_ALC_T_DEBUG, active_log); + } + active_log_len = sys_strlen(buf); + active_log = erts_alloc(ERTS_ALC_T_DEBUG, active_log_len + 1); + sys_memcpy(active_log, buf, active_log_len + 1); + erts_rwmtx_rwunlock(&erts_dist_table_rwmtx); + erts_free(ERTS_ALC_T_TMP, (void *) buf); + BIF_RET(ret); + } +# endif #endif #if defined(__GNUC__) && defined(HAVE_SOLARIS_SPARC_PERFMON) } else if (ERTS_IS_ATOM_STR("ultrasparc_set_pcr", sel)) { @@ -2411,6 +2471,9 @@ BIF_RETTYPE system_info_1(BIF_ALIST_1) #elif defined(VALGRIND) ERTS_DECL_AM(valgrind); BIF_RET(AM_valgrind); +#elif defined(ADDRESS_SANITIZER) + ERTS_DECL_AM(asan); + BIF_RET(AM_asan); #elif defined(GPROF) ERTS_DECL_AM(gprof); BIF_RET(AM_gprof); diff --git a/erts/emulator/beam/erl_binary.h b/erts/emulator/beam/erl_binary.h index ce2f7bf2ee..bd3669d896 100644 --- a/erts/emulator/beam/erl_binary.h +++ b/erts/emulator/beam/erl_binary.h @@ -369,7 +369,7 @@ erts_free_aligned_binary_bytes(byte* buf) ** These extra bytes where earlier (< R13B04) added by an alignment-bug ** in this code. Do we dare remove this in some major release (R14?) maybe? */ -#if defined(DEBUG) || defined(VALGRIND) +#if defined(DEBUG) || defined(VALGRIND) || defined(ADDRESS_SANITIZER) # define CHICKEN_PAD 0 #else # define CHICKEN_PAD (sizeof(void*) - 1) diff --git a/erts/emulator/beam/erl_sched_spec_pre_alloc.c b/erts/emulator/beam/erl_sched_spec_pre_alloc.c index 9766e76a83..d24bb727ce 100644 --- a/erts/emulator/beam/erl_sched_spec_pre_alloc.c +++ b/erts/emulator/beam/erl_sched_spec_pre_alloc.c @@ -32,6 +32,7 @@ # include "config.h" #endif +#if !defined(VALGRIND) && !defined(ADDRESS_SANITIZER) #include "erl_process.h" #include "erl_thr_progress.h" @@ -347,3 +348,4 @@ erts_sspa_process_remote_frees(erts_sspa_chunk_header_t *chdr, return res; } +#endif /* !defined(VALGRIND) && !defined(ADDRESS_SANITIZER) */ diff --git a/erts/emulator/sys/common/erl_mmap.c b/erts/emulator/sys/common/erl_mmap.c index fb46aeabcc..bb09590b1b 100644 --- a/erts/emulator/sys/common/erl_mmap.c +++ b/erts/emulator/sys/common/erl_mmap.c @@ -2153,13 +2153,18 @@ void erts_mmap_init(ErtsMemMapper* mm, ErtsMMapInit *init) { static int is_first_call = 1; - int virtual_map = 0; char *start = NULL, *end = NULL; UWord pagesize; + int virtual_map = 0; + + (void)virtual_map; + #if defined(__WIN32__) - SYSTEM_INFO sysinfo; - GetSystemInfo(&sysinfo); - pagesize = (UWord) sysinfo.dwPageSize; + { + SYSTEM_INFO sysinfo; + GetSystemInfo(&sysinfo); + pagesize = (UWord) sysinfo.dwPageSize; + } #elif defined(_SC_PAGESIZE) pagesize = (UWord) sysconf(_SC_PAGESIZE); #elif defined(HAVE_GETPAGESIZE) diff --git a/erts/emulator/sys/common/erl_mmap.h b/erts/emulator/sys/common/erl_mmap.h index c75afb14f3..7113ebc323 100644 --- a/erts/emulator/sys/common/erl_mmap.h +++ b/erts/emulator/sys/common/erl_mmap.h @@ -49,7 +49,8 @@ * See the following message on how MAP_NORESERVE was treated on FreeBSD: * <http://lists.llvm.org/pipermail/cfe-commits/Week-of-Mon-20150202/122958.html> */ -# if defined(MAP_FIXED) && (defined(MAP_NORESERVE) || defined(__FreeBSD__)) +# if (defined(MAP_FIXED) && (defined(MAP_NORESERVE) || defined(__FreeBSD__)) \ + && !defined(ADDRESS_SANITIZER)) # define ERTS_HAVE_OS_PHYSICAL_MEMORY_RESERVATION 1 # endif #endif diff --git a/erts/emulator/sys/unix/sys.c b/erts/emulator/sys/unix/sys.c index a277201056..4fc10cc4f3 100644 --- a/erts/emulator/sys/unix/sys.c +++ b/erts/emulator/sys/unix/sys.c @@ -49,6 +49,10 @@ #include <sys/ioctl.h> #endif +#ifdef ADDRESS_SANITIZER +# include <sanitizer/asan_interface.h> +#endif + #define ERTS_WANT_BREAK_HANDLING #define WANT_NONBLOCKING /* must define this to pull in defs from sys.h */ #include "sys.h" @@ -381,6 +385,9 @@ void erts_sys_sigsegv_handler(int signo) { */ int erts_sys_is_area_readable(char *start, char *stop) { +#ifdef ADDRESS_SANITIZER + return __asan_region_is_poisoned(start, stop-start) == NULL; +#else int fds[2]; if (!pipe(fds)) { /* We let write try to figure out if the pointers are readable */ @@ -395,7 +402,7 @@ erts_sys_is_area_readable(char *start, char *stop) { return 1; } return 0; - +#endif } static ERTS_INLINE int diff --git a/erts/emulator/test/alloc_SUITE.erl b/erts/emulator/test/alloc_SUITE.erl index 51406c6934..a9c816efb5 100644 --- a/erts/emulator/test/alloc_SUITE.erl +++ b/erts/emulator/test/alloc_SUITE.erl @@ -19,8 +19,8 @@ -module(alloc_SUITE). -author('rickard.green@uab.ericsson.se'). --export([all/0, suite/0, init_per_testcase/2, end_per_testcase/2]). - +-export([all/0, suite/0, init_per_testcase/2, end_per_testcase/2, + init_per_suite/1, end_per_suite/1]). -export([basic/1, coalesce/1, threads/1, @@ -47,6 +47,18 @@ all() -> bucket_mask, rbtree, mseg_clear_cache, erts_mmap, cpool, migration, cpool_opt]. +init_per_suite(Config) -> + case test_server:is_asan() of + true -> + %% No point testing own allocators under address sanitizer. + {skip, "Address sanitizer"}; + false -> + Config + end. + +end_per_suite(_Config) -> + ok. + init_per_testcase(Case, Config) when is_list(Config) -> [{testcase, Case},{debug,false}|Config]. diff --git a/erts/emulator/test/bif_SUITE.erl b/erts/emulator/test/bif_SUITE.erl index 8eb2bb9719..fd47688628 100644 --- a/erts/emulator/test/bif_SUITE.erl +++ b/erts/emulator/test/bif_SUITE.erl @@ -790,7 +790,12 @@ erlang_halt(Config) when is_list(Config) -> % This test triggers a segfault when dumping a crash dump % to make sure that we can handle it properly. + + %% Prevent address sanitizer from catching SEGV in slave node + AsanOpts = add_asan_opt("handle_segv=0"), {ok,N4} = slave:start(H, halt_node4), + reset_asan_opts(AsanOpts), + CrashDump = filename:join(proplists:get_value(priv_dir,Config), "segfault_erl_crash.dump"), true = rpc:call(N4, os, putenv, ["ERL_CRASH_DUMP",CrashDump]), @@ -808,6 +813,25 @@ erlang_halt(Config) when is_list(Config) -> ok end. +add_asan_opt(Opt) -> + case test_server:is_asan() of + true -> + case os:getenv("ASAN_OPTIONS") of + false -> + os:putenv("ASAN_OPTIONS", Opt), + undefined; + AO -> + os:putenv("ASAN_OPTIONS", AO ++ [$: | Opt]), + AO + end; + _ -> + false + end. + +reset_asan_opts(false) -> ok; +reset_asan_opts(undefined) -> os:unsetenv("ASAN_OPTIONS"); +reset_asan_opts(AO) -> os:putenv("ASAN_OPTIONS", AO). + wait_until_stable_size(_File,-10) -> {error,enoent}; wait_until_stable_size(File,PrevSz) -> diff --git a/erts/emulator/test/erts_debug_SUITE.erl b/erts/emulator/test/erts_debug_SUITE.erl index 71df875efc..844d344ef3 100644 --- a/erts/emulator/test/erts_debug_SUITE.erl +++ b/erts/emulator/test/erts_debug_SUITE.erl @@ -258,7 +258,10 @@ alloc_blocks_size(Config) when is_list(Config) -> ok = rpc:call(Node, ?MODULE, do_alloc_blocks_size, []), true = test_server:stop_node(Node) end, - F("+Meamax"), + case test_server:is_asan() of + false -> F("+Meamax"); + true -> skip + end, F("+Meamin"), F(""), ok. diff --git a/erts/emulator/test/hash_SUITE.erl b/erts/emulator/test/hash_SUITE.erl index c4a700d1a7..86b4460b38 100644 --- a/erts/emulator/test/hash_SUITE.erl +++ b/erts/emulator/test/hash_SUITE.erl @@ -640,13 +640,18 @@ test_phash2_plus_bin_helper2(Bin, TransformerFun, ExtraBytes, ExtraBits, Expecte end. run_when_enough_resources(Fun) -> - case {total_memory(), erlang:system_info(wordsize)} of - {Mem, 8} when is_integer(Mem) andalso Mem >= 31 -> + Bits = 8 * erlang:system_info({wordsize,external}), + Mem = total_memory(), + Build = erlang:system_info(build_type), + + if Bits =:= 64, is_integer(Mem), Mem >= 31, + Build =/= valgrind, Build =/= asan -> Fun(); - {Mem, WordSize} -> + + true -> {skipped, - io_lib:format("Not enough resources (System Memory >= ~p, Word Size = ~p)", - [Mem, WordSize])} + io_lib:format("Not enough resources (System Memory = ~p, Bits = ~p, Build = ~p)", + [Mem, Bits, Build])} end. %% Total memory in GB diff --git a/erts/emulator/test/os_signal_SUITE.erl b/erts/emulator/test/os_signal_SUITE.erl index 6bafb0e18c..7bd8985dc7 100644 --- a/erts/emulator/test/os_signal_SUITE.erl +++ b/erts/emulator/test/os_signal_SUITE.erl @@ -275,6 +275,15 @@ t_sigalrm(_Config) -> ok. t_sigchld_fork(_Config) -> + case test_server:is_asan() of + true -> + %% Avoid false leak reports from forked process + {skip, "Address sanitizer"}; + false -> + sigchld_fork() + end. + +sigchld_fork() -> Pid1 = setup_service(), ok = os:set_signal(sigchld, handle), {ok,OsPid} = os_signal_SUITE:fork(), diff --git a/erts/etc/unix/cerl.src b/erts/etc/unix/cerl.src index 575ef80e88..f617736e43 100644 --- a/erts/etc/unix/cerl.src +++ b/erts/etc/unix/cerl.src @@ -40,6 +40,7 @@ # -xxgdb FIXME currently disabled # -gcov Run emulator compiled for gcov # -valgrind Run emulator compiled for valgrind +# -asan Run emulator compiled for address-sanitizer # -lcnt Run emulator compiled for lock counting # -icount Run emulator compiled for instruction counting # -rr Run emulator under "rr record" @@ -73,8 +74,8 @@ GDBBP= GDBARGS= TYPE= FLAVOR= -debug= run_valgrind=no +run_asan=no run_rr=no skip_erlexec=no @@ -203,6 +204,12 @@ while [ $# -gt 0 ]; do run_valgrind=yes skip_erlexec=yes ;; + "-asan") + shift + cargs="$cargs -asan" + run_asan=yes + TYPE=.asan + ;; "-emu_type") shift cargs="$cargs -emu_type $1" @@ -265,6 +272,28 @@ if [ $skip_erlexec = yes ]; then set -- $beam_args IFS="$SAVE_IFS" fi +if [ $run_asan = yes ]; then + # Leak sanitizer options + if [ "x${LSAN_OPTIONS#*suppressions=}" = "x$LSAN_OPTIONS" ]; then + export LSAN_OPTIONS + if [ "x$ERL_TOP" != "x" ]; then + LSAN_OPTIONS="$LSAN_OPTIONS:suppressions=$ERL_TOP/erts/emulator/asan/suppress" + else + echo "No leak-sanitizer suppression file found in \$LSAN_OPTIONS" + echo "and \$ERL_TOP not set." + fi + fi + # Address sanitizer options + export ASAN_OPTIONS + if [ "x$ASAN_LOG_DIR" != "x" ]; then + if [ "x${ASAN_OPTIONS#*log_path=}" = "x$ASAN_OPTIONS" ]; then + ASAN_OPTIONS="$ASAN_OPTIONS:log_path=$ASAN_LOG_DIR/$EMU_NAME-$ASAN_LOGFILE_PREFIX-0" + fi + fi + if [ "x${ASAN_OPTIONS#*halt_on_error=}" = "x$ASAN_OPTIONS" ]; then + ASAN_OPTIONS="$ASAN_OPTIONS:halt_on_error=false" + fi +fi if [ "x$GDB" = "x" ]; then if [ $run_valgrind = yes ]; then valversion=`valgrind --version` diff --git a/erts/lib_src/Makefile.in b/erts/lib_src/Makefile.in index 45ff7639d4..2851c8cb02 100644 --- a/erts/lib_src/Makefile.in +++ b/erts/lib_src/Makefile.in @@ -66,6 +66,11 @@ CFLAGS=@DEBUG_CFLAGS@ -DVALGRIND TYPE_SUFFIX=.valgrind PRE_LD= else +ifeq ($(TYPE),asan) +CFLAGS=@DEBUG_CFLAGS@ +TYPE_SUFFIX=.asan +PRE_LD= +else ifeq ($(TYPE),gprof) CFLAGS += -DGPROF -pg TYPE_SUFFIX=.gprof @@ -99,6 +104,7 @@ endif endif endif endif +endif OPSYS=@OPSYS@ sol2CFLAGS= diff --git a/lib/common_test/src/test_server.erl b/lib/common_test/src/test_server.erl index 20c229dde4..afc5373278 100644 --- a/lib/common_test/src/test_server.erl +++ b/lib/common_test/src/test_server.erl @@ -21,7 +21,7 @@ -define(DEFAULT_TIMETRAP_SECS, 60). %%% TEST_SERVER_CTRL INTERFACE %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% --export([run_test_case_apply/1,init_target_info/0,init_valgrind/0]). +-export([run_test_case_apply/1,init_target_info/0,init_memory_checker/0]). -export([cover_compile/1,cover_analyse/2]). %%% TEST_SERVER_SUP INTERFACE %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -48,10 +48,8 @@ -export([is_cover/0,is_debug/0,is_commercial/0]). -export([break/1,break/2,break/3,continue/0,continue/1]). +-export([is_valgrind/0, is_asan/0]). -%%% DEBUGGER INTERFACE %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% --export([valgrind_new_leaks/0, valgrind_format/2, - is_valgrind/0]). %%% PRIVATE EXPORTED %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -export([]). @@ -60,6 +58,7 @@ -include("test_server_internal.hrl"). -include_lib("kernel/include/file.hrl"). + init_target_info() -> [$.|Emu] = code:objfile_extension(), {_, OTPRel} = init:script_id(), @@ -73,8 +72,8 @@ init_target_info() -> username=test_server_sup:get_username(), cookie=atom_to_list(erlang:get_cookie())}. -init_valgrind() -> - valgrind_new_leaks(). +init_memory_checker() -> + check_memory_leaks(). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -367,19 +366,50 @@ stick_all_sticky(Node,Sticky) -> %% cover. run_test_case_apply({CaseNum,Mod,Func,Args,Name,RunInit,TimetrapData}) -> - case is_valgrind() of - false -> - ok; - true -> - valgrind_format("Test case #~w ~w:~w/1", [CaseNum, Mod, Func]), - os:putenv("VALGRIND_LOGFILE_INFIX",atom_to_list(Mod)++"."++ - atom_to_list(Func)++"-") - end, + MC = case {Func, memory_checker()} of + {init_per_suite, _} -> none; % skip init/end_per_suite/group + {init_per_group, _} -> none; % as CaseNum is always 0 + {end_per_group, _} -> none; + {end_per_suite, _} -> none; + {_, valgrind} -> + valgrind_format("Test case #~w ~w:~w/1", [CaseNum, Mod, Func]), + os:putenv("VALGRIND_LOGFILE_INFIX",atom_to_list(Mod)++"."++ + atom_to_list(Func)++"-"), + valgrind; + {_, asan} -> + %% Address sanitizer does not support printf in log file + %% but it lets us change the log file on the fly. So we use + %% that to give each test case its own log file. + case asan_take_logpath() of + false -> false; + {LogPath, OtherOpts} -> + LogDir = filename:dirname(LogPath), + LogFile = filename:basename(LogPath), + [Exe, App | _ ] = string:lexemes(LogFile, "-"), + NewLogFile = io_lib:format("~s-~s-tc-~4..0w-~w-~w", + [Exe,App,CaseNum, Mod, Func]), + NewLogPath = filename:join(LogDir, NewLogFile), + + %% Do leak check and then change asan log file + %% for this running beam executable. + erlang:system_info({memory_checker, check_leaks}), + _PrevLog = erlang:system_info({memory_checker, log, NewLogPath}), + + %% Set log file name for subnodes + %% that may be created by this test case + NewOpts = asan_make_opts(["log_path="++NewLogPath++".subnode" + | OtherOpts]), + os:putenv("ASAN_OPTIONS", NewOpts) + end, + asan; + {_, none} -> + node + end, ProcBef = erlang:system_info(process_count), Result = run_test_case_apply(Mod, Func, Args, Name, RunInit, TimetrapData), ProcAft = erlang:system_info(process_count), - valgrind_new_leaks(), + check_memory_leaks(MC), DetFail = get(test_server_detected_fail), {Result,DetFail,ProcBef,ProcAft}. @@ -2053,7 +2083,8 @@ timetrap_scale_factor() -> { 3, fun() -> has_superfluous_schedulers() end}, { 6, fun() -> is_debug() end}, {10, fun() -> is_cover() end}, - {10, fun() -> is_valgrind() end} + {10, fun() -> is_valgrind() end}, + {2, fun() -> is_asan() end} ]). timetrap_scale_factor(Scales) -> @@ -2962,22 +2993,36 @@ is_commercial() -> %% %% Returns true if valgrind is running, else false is_valgrind() -> - case catch erlang:system_info({valgrind, running}) of - {'EXIT', _} -> false; - Res -> Res + memory_checker() =:= valgrind. + +%% Returns true if address-sanitizer is running, else false +is_asan() -> + memory_checker() =:= asan. + +%% Returns the error checker running (valgrind | asan | none). +memory_checker() -> + case catch erlang:system_info({memory_checker, running}) of + {'EXIT', _} -> none; + EC -> EC end. + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% DEBUGGER INTERFACE %% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% valgrind_new_leaks() -> ok +%% check_memory_leaks() -> ok %% -%% Checks for new memory leaks if Valgrind is active. -valgrind_new_leaks() -> - catch erlang:system_info({valgrind, memory}), +%% Checks for memory leaks if Valgrind or Address-sanitizer is active. +check_memory_leaks() -> + check_memory_leaks(memory_checker()). + +check_memory_leaks(valgrind) -> + catch erlang:system_info({memory_checker, check_leaks}), + ok; +check_memory_leaks(_) -> ok. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -2987,9 +3032,31 @@ valgrind_new_leaks() -> %% %% Outputs the formatted string to Valgrind's logfile,if Valgrind is active. valgrind_format(Format, Args) -> - (catch erlang:system_info({valgrind, io_lib:format(Format, Args)})), + (catch erlang:system_info({valgrind, print, io_lib:format(Format, Args)})), ok. +asan_take_logpath() -> + case os:getenv("ASAN_OPTIONS") of + false -> false; + S -> + Opts = string:lexemes(S, ":"), + asan_take_logpath_loop(Opts, []) + end. + +asan_take_logpath_loop(["log_path="++LogPath | T], Acc) -> + {LogPath, T ++ Acc}; +asan_take_logpath_loop([Opt | T], Acc) -> + asan_take_logpath_loop(T, [Opt | Acc]); +asan_take_logpath_loop([], _) -> + false. + +asan_make_opts([A|T]) -> + asan_make_opts(T, A). + +asan_make_opts([], Acc) -> + Acc; +asan_make_opts([A|T], Acc) -> + asan_make_opts(T, A ++ [$: | Acc]). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/lib/common_test/src/test_server_ctrl.erl b/lib/common_test/src/test_server_ctrl.erl index 995594dd59..dbd5537206 100644 --- a/lib/common_test/src/test_server_ctrl.erl +++ b/lib/common_test/src/test_server_ctrl.erl @@ -2195,7 +2195,7 @@ do_add_end_per_suite_and_skip(LastMod, LastRef, Mod, FwMod) -> %% Runs the specified tests, then displays/logs the summary. run_test_cases(TestSpec, Config, TimetrapData) -> - test_server:init_valgrind(), + test_server:init_memory_checker(), case lists:member(no_src, get(test_server_logopts)) of true -> ok; diff --git a/lib/common_test/test_server/ts_run.erl b/lib/common_test/test_server/ts_run.erl index 7e12b9652c..ce454dce9c 100644 --- a/lib/common_test/test_server/ts_run.erl +++ b/lib/common_test/test_server/ts_run.erl @@ -197,17 +197,25 @@ make_command(Vars, Spec, State) -> {ok,Cwd} = file:get_cwd(), TestDir = State#state.test_dir, TestPath = filename:nativename(TestDir), - Erl = case os:getenv("TS_RUN_VALGRIND") of + Erl = case os:getenv("TS_RUN_EMU") of false -> ct:get_progname(); - _ -> + "valgrind" -> case State#state.file of Dir when is_list(Dir) -> os:putenv("VALGRIND_LOGFILE_PREFIX", Dir++"-"); _ -> ok end, - "cerl -valgrind" + "cerl -valgrind"; + "asan" -> + case State#state.file of + App when is_list(App) -> + os:putenv("ASAN_LOGFILE_PREFIX", App); + _ -> + ok + end, + "cerl -asan" end, Naming = case ts_lib:var(longnames, Vars) of @@ -261,9 +269,10 @@ run_batch(Vars, _Spec, State) -> ts_lib:progress(Vars, 1, "Command: ~ts~n", [Command]), io:format(user, "Command: ~ts~n",[Command]), Port = open_port({spawn, Command}, [stream, in, eof, exit_status]), - Timeout = 30000 * case os:getenv("TS_RUN_VALGRIND") of + Timeout = 30000 * case os:getenv("TS_RUN_EMU") of false -> 1; - _ -> 100 + "valgrind" -> 100; + "asan" -> 2 end, tricky_print_data(Port, Timeout). diff --git a/lib/crypto/c_src/Makefile.in b/lib/crypto/c_src/Makefile.in index b9ecc39f58..f962b7b9ad 100644 --- a/lib/crypto/c_src/Makefile.in +++ b/lib/crypto/c_src/Makefile.in @@ -59,11 +59,17 @@ TYPEMARKER = .gprof TYPE_EXTRA_CFLAGS = -DGPROF -pg TYPE_FLAGS = $(CFLAGS) $(TYPE_EXTRA_CFLAGS) else +ifeq ($(TYPE),asan) +TYPEMARKER = .asan +TYPE_FLAGS = $(CFLAGS) -fsanitize=address -fsanitize-recover=address -fno-omit-frame-pointer -DADDRESS_SANITIZER +LDFLAGS += -fsanitize=address +else TYPEMARKER = TYPE_FLAGS = $(CFLAGS) endif endif endif +endif # ---------------------------------------------------- # Release directory specification @@ -158,7 +164,7 @@ ALL_STATIC_CFLAGS = @DED_STATIC_CFLAGS@ $(TYPE_EXTRA_CFLAGS) $(CONFIGURE_ARGS) $ _create_dirs := $(shell mkdir -p $(OBJDIR) $(LIBDIR)) -debug opt valgrind: $(NIF_LIB) $(CALLBACK_LIB) $(TEST_ENGINE_LIB) +debug opt valgrind asan: $(NIF_LIB) $(CALLBACK_LIB) $(TEST_ENGINE_LIB) static_lib: $(NIF_ARCHIVE) diff --git a/lib/crypto/c_src/crypto.c b/lib/crypto/c_src/crypto.c index 7bf6265166..5c0a1ccf6a 100644 --- a/lib/crypto/c_src/crypto.c +++ b/lib/crypto/c_src/crypto.c @@ -162,8 +162,8 @@ static int initialize(ErlNifEnv* env, ERL_NIF_TERM load_info) const ERL_NIF_TERM* tpl_array; int vernum; ErlNifBinary lib_bin; - char lib_buf[1000]; #ifdef HAVE_DYNAMIC_CRYPTO_LIB + char lib_buf[1000]; void *handle; #endif diff --git a/lib/crypto/c_src/info.c b/lib/crypto/c_src/info.c index 573039203c..1d7e744995 100644 --- a/lib/crypto/c_src/info.c +++ b/lib/crypto/c_src/info.c @@ -26,6 +26,8 @@ char *crypto_callback_name = "crypto_callback.debug"; # elif defined(VALGRIND) char *crypto_callback_name = "crypto_callback.valgrind"; +# elif defined(ADDRESS_SANITIZER) +char *crypto_callback_name = "crypto_callback.asan"; # else char *crypto_callback_name = "crypto_callback"; # endif diff --git a/lib/crypto/doc/src/Makefile b/lib/crypto/doc/src/Makefile index f48a79e8d1..b4926d6d7c 100644 --- a/lib/crypto/doc/src/Makefile +++ b/lib/crypto/doc/src/Makefile @@ -48,4 +48,4 @@ TOP_SPECS_FILE = specs.xml include $(ERL_TOP)/make/doc.mk -valgrind: +valgrind asan: diff --git a/lib/crypto/src/Makefile b/lib/crypto/src/Makefile index 1753ba4f36..c3f1c859e5 100644 --- a/lib/crypto/src/Makefile +++ b/lib/crypto/src/Makefile @@ -61,7 +61,7 @@ ERL_COMPILE_FLAGS += -DCRYPTO_VSN=\"$(VSN)\" -Werror -I../include # Targets # ---------------------------------------------------- -debug opt valgrind: $(TARGET_FILES) $(APP_TARGET) $(APPUP_TARGET) +debug opt valgrind asan: $(TARGET_FILES) $(APP_TARGET) $(APPUP_TARGET) clean: rm -f $(TARGET_FILES) $(APP_TARGET) $(APPUP_TARGET) diff --git a/lib/crypto/src/crypto.erl b/lib/crypto/src/crypto.erl index f83866803b..a9c18a3779 100644 --- a/lib/crypto/src/crypto.erl +++ b/lib/crypto/src/crypto.erl @@ -2494,10 +2494,10 @@ get_test_engine() -> end. check_otp_test_engine(LibDir) -> - case filelib:wildcard("otp_test_engine*", LibDir) of - [] -> + case choose_otp_test_engine(LibDir) of + false -> {error, notexist}; - [LibName|_] -> % In case of Valgrind there could be more than one + LibName -> LibPath = filename:join(LibDir,LibName), case filelib:is_file(LibPath) of true -> @@ -2508,3 +2508,20 @@ check_otp_test_engine(LibDir) -> end. +choose_otp_test_engine(LibDir) -> + LibNames = filelib:wildcard("otp_test_engine.*", LibDir), + Type = atom_to_list(erlang:system_info(build_type)), + choose_otp_test_engine(LibNames, Type, false). + +choose_otp_test_engine([LibName | T], Type, Acc) -> + case string:lexemes(LibName, ".") of + [_, Type, _SO] -> + LibName; %% Choose typed if exists (valgrind,asan) + [_, _SO] -> + %% Fallback on typeless (opt) + choose_otp_test_engine(T, Type, LibName); + _ -> + choose_otp_test_engine(T, Type, Acc) + end; +choose_otp_test_engine([], _, Acc) -> + Acc. diff --git a/lib/runtime_tools/test/erts_alloc_config_SUITE.erl b/lib/runtime_tools/test/erts_alloc_config_SUITE.erl index 6ae51d9a26..9ab61b89d2 100644 --- a/lib/runtime_tools/test/erts_alloc_config_SUITE.erl +++ b/lib/runtime_tools/test/erts_alloc_config_SUITE.erl @@ -25,7 +25,9 @@ -include_lib("common_test/include/ct.hrl"). %-compile(export_all). --export([all/0, suite/0, init_per_testcase/2, end_per_testcase/2]). +-export([all/0, suite/0, + init_per_suite/1, end_per_suite/1, + init_per_testcase/2, end_per_testcase/2]). %% Testcases -export([basic/1]). @@ -40,6 +42,18 @@ suite() -> all() -> [basic]. +init_per_suite(Config) -> + case test_server:is_asan() of + true -> + %% No point testing own allocators under address sanitizer. + {skip, "Address sanitizer"}; + false -> + Config + end. + +end_per_suite(_Config) -> + ok. + init_per_testcase(Case, Config) when is_list(Config) -> [{testcase, Case}, {erl_flags_env, save_env()} | Config]. diff --git a/lib/tools/test/instrument_SUITE.erl b/lib/tools/test/instrument_SUITE.erl index de7a23d16e..936eb19327 100644 --- a/lib/tools/test/instrument_SUITE.erl +++ b/lib/tools/test/instrument_SUITE.erl @@ -19,7 +19,7 @@ %% -module(instrument_SUITE). --export([all/0, suite/0]). +-export([all/0, suite/0, init_per_suite/1, end_per_suite/1]). -export([allocations_enabled/1, allocations_disabled/1, allocations_ramv/1, carriers_enabled/1, carriers_disabled/1]). @@ -37,6 +37,19 @@ all() -> [allocations_enabled, allocations_disabled, allocations_ramv, carriers_enabled, carriers_disabled]. +init_per_suite(Config) -> + case test_server:is_asan() of + true -> + %% No point testing own allocators under address sanitizer. + {skip, "Address sanitizer"}; + false -> + Config + end. + +end_per_suite(_Config) -> + ok. + + -define(GENERATED_SBC_BLOCK_COUNT, 1000). -define(GENERATED_MBC_BLOCK_COUNT, ?GENERATED_SBC_BLOCK_COUNT). diff --git a/make/otp_subdir.mk b/make/otp_subdir.mk index 19c744955c..f9b993e048 100644 --- a/make/otp_subdir.mk +++ b/make/otp_subdir.mk @@ -20,12 +20,12 @@ # Make include file for otp .PHONY: debug opt lcnt release docs release_docs tests release_tests \ - clean depend valgrind static_lib + clean depend valgrind asan static_lib # # Targets that don't affect documentation directories # -opt debug lcnt release docs release_docs tests release_tests clean depend valgrind static_lib xmllint: +opt debug lcnt release docs release_docs tests release_tests clean depend valgrind asan static_lib xmllint: @set -e ; \ app_pwd=`pwd` ; \ if test -f vsn.mk; then \ diff --git a/make/run_make.mk b/make/run_make.mk index d66339d28e..4185927f72 100644 --- a/make/run_make.mk +++ b/make/run_make.mk @@ -29,9 +29,9 @@ include $(ERL_TOP)/make/output.mk include $(ERL_TOP)/make/target.mk -.PHONY: valgrind +.PHONY: valgrind asan -opt debug valgrind gcov gprof lcnt frmptr icount: +opt debug valgrind asan gcov gprof lcnt frmptr icount: $(make_verbose)$(MAKE) -f $(TARGET)/Makefile TYPE=$@ emu jit: |