c++static-librariesaddress-sanitizer

__asan_default_options() ignored when used in a STATIC library and linked against an executable


I'm trying to globally configure AddressSanitizer (ASan) for multiple executables in my C++ project by defining the __asan_default_options() function, like this:

extern "C" const char* __asan_default_options() {
    return "allow_user_poisoning=false";
}

I placed this function in a .cpp file, built it into a static library, and then linked that library into an executable. However, ASan does not seem to respect the options.

Note: If I instead add the .cpp file directly to the sources of each executable, it works as expected.

Note: I also found that using a CMake OBJECT library works. For example:

add_library(configAsan OBJECT asan_flags.cpp)
target_link_libraries(my_exec PRIVATE configAsan)

Question: Why does defining __asan_default_options() in a static library not work ?

I tried

add_library(configAsan STATIC asan_flags.cpp)
target_link_libraries(my_exec PRIVATE configAsan)

where my executable looks like this:

int main(int argc, char* argv[]) {
 int* p = new int[4];
 __asan_poison_memory_region(p + 1, 3 * sizeof(int));
 std::cout << "Accessing poisoned memory: " << p[1] << "\n"; 
 delete[] p; 
 return 0; 
}

Expected No use after poison error when running my_exec but got: Error message


Solution

  • Refer to the Stackoverflow tag wiki on static libraries

    From that you will learn that if an object file file.o is archived in a static library libstat.a and then libstat.a is input to the linkage of an executable, the linker by default will extract file.o from libstat.a and link file.o into the executable only if file.o provides a definition of at least one symbol that has been referenced in some object file already linked into the executable, but not yet defined by any file that has been linked. In short, file.o will be extracted and linked only if it provides a definition that the linkage already needs to resolve some symbol reference.

    The file asan_flags.o, compiled from your asan_flags.cpp and archived as libconfigAsan.a(asanFlags.o), defines no symbol that has been referenced in any file already linked when the linker examines your libconfigAsan.a, so asan_flags.o is not extracted and linked. It might as well not exist.

    Later in the linkage of your executable, the linker consumes libasan.so itself, which serves to resolve your reference to __asan_poison_memory_region and introduces a call to __asan_default_options into the linkage, which must be resolved. But by now your definition of that function in libconfigAsan.a(asan_flags.o) has been passed over and ignored. The linker finds a default definition of __asan_default_options in libasan.so itself and binds the reference to that definition - which is in fact a no-op. Hence the outcome you observe.

    To override that no-op default definition with your own, you must make asan_flags.o be linked before libasan.so is consumed. One good way to do that is simply to input asan_flags.o explicitly to the linkage, not as an archive member of a static library. This is what you have achieved in CMake by building asan_flags.cpp as a CMake object library: that just compiles it to asan_flags.o. Any object file that is input to the linker explicitly is linked into the executable unconditionally. If an explicit object file was not unconditionally linked then no symbol references could get into the executable unconditionally and linkage could never get started.

    This way, when libasan.so is consumed, your definition of __asan_default_options is already linked and references to that symbol will be bound to that definition: the linker will not seek another definition of a symbol that is already defined. (Even if you chose to link libasan statically, its default definition of __asan_default_options cannot possibly provoke a multiple definition error in collision with your own, because the default definition is weak and will still be overriden by your own.)