I think I figured out the issue. From the build.ninja file, I found:
build lib\asan\CMakeFiles\RTAsan_dynamic.i386.dir\asan_malloc_win.cc.obj:
CXX_COMPILER E$:\llvm\compiler-rt\lib\asan\asan_malloc_win.cc
DEFINES = -DASAN_DYNAMIC=1 -DASAN_HAS_EXCEPTIONS=1
-DINTERCEPTION_DYNAMIC_CRT -D__func__=__FUNCTION__
DEP_FILE = lib/asan/CMakeFiles/RTAsan_dynamic.i386.dir/asan_malloc_win.cc.obj.d
FLAGS = /DWIN32 /D_WINDOWS /W3 /GR /EHsc /W3 /MT /O2 /Ob2 /D NDEBUG
-IE:\llvm\compiler-rt\lib\asan\.. /machine:X86 /DWIN32
/D_WINDOWS /W3 /GR /EHsc /W3 /Oy- /GS- /VERBOSE /Zi /wd4391 /wd4722
/wd4291 /wd4800 /GR- /DEBUG
OBJECT_DIR = lib\asan\CMakeFiles\RTAsan_dynamic.i386.dir
TARGET_PDB = ""
Notice how it's passing the /MT flag instead of /MD? That's different
from MSVC, which is using this for its command line options:
/GS- /TP /analyze- /W3 /wd"4391" /wd"4722" /wd"4291" /wd"4800"
/Zc:wchar_t /I"E:\llvm\compiler-rt\lib\asan\.." /Zi /Gm- /O2 /Ob1
/Fd"RTAsan_dynamic.i386.dir\RelWithDebInfo\vc120.pdb" /fp:precise /D
"WIN32" /D "_WINDOWS" /D "NDEBUG" /D "EBUG" /D "ASAN_HAS_EXCEPTIONS=1"
/D "ASAN_DYNAMIC=1" /D "INTERCEPTION_DYNAMIC_CRT" /D
"__func__=__FUNCTION__" /D "CMAKE_INTDIR=\"RelWithDebInfo\"" /D
"_MBCS" /errorReport:prompt /WX- /Zc:forScope /GR- /Gd /Oy- /MD
/Fa"RelWithDebInfo/" /EHsc /nologo
/Fo"RTAsan_dynamic.i386.dir\RelWithDebInfo\"
/Fp"RTAsan_dynamic.i386.dir\RelWithDebInfo\RTAsan_dynamic.i386.pch"
(Note how it uses /MD)
So, basically, ninja is linking as though everything was being
compiled for a static library, when we're actually building a DLL
(from what I can tell, anyway).