This is an archive of the discontinued LLVM Phabricator instance.

[ASAN] fix crash in GetGlobalsForAddress after dlclose
Needs ReviewPublic

Authored by Lekensteyn on Jun 11 2018, 9:49 AM.

Details

Reviewers
kcc
eugenis
Summary

Calling __asan_describe_address after unloading any library through
dlclose or FreeLibrary currently results in a crash due to accessing
unmapped memory (the Global structure in list_of_all_globals).

As list_of_all_globals is only accessed under a lock, it is safe to
remove items in UnregisterGlobal.

To prevent a significant slowdown (0.8 secs before this patch, 5.2 secs
for an initial O(n^2) version that naively walks the list), optimize it:

  1. Reverse the loop (reduces time to 1.0 sec), 2. Cache the start of the

loop (restores original performance). Both changes make O(n) possible.

The above test was performed on a project that starts with 881337
globals. It would load 24125 additional globals through 14 plugins.
I confirmed through gdb that the size of list_of_all_globals indeed
shrinks (the size at exit matches with the size at startup).

Fixes https://github.com/google/sanitizers/issues/741
Fixes https://github.com/google/sanitizers/issues/963

Diff Detail

Event Timeline

Lekensteyn created this revision.Jun 11 2018, 9:49 AM
Herald added subscribers: Restricted Project, llvm-commits, kubamracek. · View Herald TranscriptJun 11 2018, 9:49 AM
Lekensteyn added a comment.EditedJun 11 2018, 10:00 AM

Note: the issue was reproduced on both Linux (clang from trunk) and Windows (Clang 6.0.0). The fix was tested for Linux, but not on Windows (if someone has a setup ready, please test!). I also confirmed that the latest patch passes the test on Windows 7 and 10 (x64) and that without the patch, it crashes.

ASAN_OPTIONS=handle_segv=0 is set to avoid a hang due to a recursive invocation on the unfixed library:

AddressSanitizer:DEADLYSIGNAL
#0  __sanitizer::BlockingMutex::Lock () projects/compiler-rt/lib/sanitizer_common/sanitizer_linux.cc:665
#1  0x00000000004f2394 in __sanitizer::ThreadRegistry::Lock () projects/compiler-rt/lib/asan/../sanitizer_common/sanitizer_thread_registry.h:92
#2  __asan::ScopedInErrorReport::ScopedInErrorReport () projects/compiler-rt/lib/asan/asan_report.cc:131
#3  __asan::ReportDeadlySignal () projects/compiler-rt/lib/asan/asan_report.cc:212
#4  0x00000000004f0e25 in __asan::AsanOnDeadlySignal () projects/compiler-rt/lib/asan/asan_posix.cc:38
#5  <signal handler called>
#6  0x0000000000435e99 in __asan::GetGlobalsForAddress () projects/compiler-rt/lib/asan/asan_flags.h:42
#7  0x000000000042c153 in __asan::GetGlobalAddressInformation () projects/compiler-rt/lib/asan/asan_descriptions.cc:307
#8  __asan::PrintAddressDescription () projects/compiler-rt/lib/asan/asan_descriptions.cc:489
#9  0x00000000004f9d96 in __asan_describe_address () projects/compiler-rt/lib/asan/asan_report.cc:486
#10 0x000000000052b649 in main () projects/compiler-rt/test/asan/TestCases/Posix/dlclose-globals.cc:30

I have not done a measurement against projects that have many globals and unload large libraries with many globals. If the most recently loaded library is unloaded first, then performance should not be that bad as the globals are expected to be in the front of the list.

Lekensteyn edited the summary of this revision. (Show Details)

Since performance was pretty bad at exit (see https://github.com/google/sanitizers/issues/963#issuecomment-396323984), I decided that optimization is mandatory for this version. UnregisterGlobals is apparently not only called for dlclose, but also for other shared libaries.

I have no test for the *order* of UnregisterGlobal, this could potentially result in performance regressions that the optimizations were trying to address.
If necessary, I can check that through ASAN_OPTIONS=report_globals=2. I was however not sure whether the order is deterministic. For example, given extern "C" { const char *a = "foo"; const int b = 3; }, can I be sure that these are always registered as a, then b? (And will it subsequently be unregistered as such)?