Skip to content

Commit 246234a

Browse files
[asan][windows] Eliminate the static asan runtime on windows (#81677)
This is one of the major changes we (Microsoft) have made in the version of asan we ship with Visual Studio. @amyw-msft wrote a blog post outlining this work at https://devblogs.microsoft.com/cppblog/msvc-address-sanitizer-one-dll-for-all-runtime-configurations/ > With Visual Studio 2022 version 17.7 Preview 3, we have refactored the MSVC Address Sanitizer (ASan) to depend on one runtime DLL regardless of the runtime configuration. This simplifies project onboarding and supports more scenarios, particularly for projects statically linked (/MT, /MTd) to the C Runtimes. However, static configurations have a new dependency on the ASan runtime DLL. > Summary of the changes: > ASan now works with /MT or /MTd built DLLs when the host EXE was not compiled with ASan. This includes Windows services, COM components, and plugins. Configuring your project with ASan is now simpler, since your project doesn’t need to uniformly specify the same [runtime configuration](https://learn.microsoft.com/en-us/cpp/build/reference/md-mt-ld-use-run-time-library?view=msvc-170) (/MT, /MTd, /MD, /MDd). ASan workflows and pipelines for /MT or /MTd built projects will need to ensure the ASan DLL (clang_rt.asan_dynamic-<arch>.dll) is available on PATH. The names of the ASan .lib files needed by the linker have changed (the linker normally takes care of this if not manually specifying lib names via /INFERASANLIBS) You cannot mix ASan-compiled binaries from previous versions of the MSVC Address Sanitizer (this is always true, but especially true in this case). Here's the description of these changes from our internal PR 1. Build one DLL that includes everything debug mode needs (not included here, already contributed upstream). * Remove #if _DEBUG checks everywhere. * In some places, this needed to be replaced with a runtime check. In asan_win.cpp, IsDebugRuntimePresent was added where we are searching for allocations prior to ASAN initialization. * In asan_win_runtime_functions.cpp and interception_win.cpp, we need to be aware of debug runtime DLLs even when not built with _DEBUG. 2. Redirect statically linked functions to the ASAN DLL for /MT * New exports for each of the C allocation APIs so that the statically linked portion of the runtime can call them (see asan_malloc_win.cpp, search MALLOC_DLL_EXPORT). Since we want our stack trace information to be accurate and without noise, this means we need to capture stack frame info from the original call and tell it to our DLL export. For this, I have reused the __asan_win_new_delete_data used for op new/delete support from asan_win_new_delete_thunk_common.h and moved it into asan_win_thunk_common.h renamed as __asan_win_stack_data. * For the C allocation APIs, a new file is included in the statically-linked /WHOLEARCHIVE lib - asan_malloc_win_thunk.cpp. These functions simply provide definitions for malloc/free/etc to be used instead of the UCRT's definitions for /MT and instead call the ASAN DLL export. /INFERASANLIBS ensures libucrt.lib will not take precedence via /WHOLEARCHIVE. * For other APIs, the interception code was called, so a new export is provided: __sanitizer_override_function. __sanitizer_override_function_by_addr is also provided to support __except_handler4 on x86 (due to the security cookie being per-module). 3. Support weak symbols for /MD * We have customers (CoreCLR) that rely on this behavior and would force /MT to get it. * There was sanitizer_win_weak_interception.cpp before, which did some stuff for setting up the .WEAK section, but this only worked on /MT. Now stuff registered in the .WEAK section is passed to the ASAN DLL via new export __sanitizer_register_weak_function (impl in sanitizer_win_interception.cpp). Unlike linux, multiple weak symbol registrations are possible here. Current behavior is to give priority on module load order such that whoever loads last (so priority is given to the EXE) will have their weak symbol registered. * Unfortunately, the registration can only occur during the user module startup, which is after ASAN DLL startup, so any weak symbols used by ASAN during initialization will not be picked up. This is most notable for __asan_default_options and friends (see asan_flags.cpp). A mechanism was made to add a callback for when a certain weak symbol was registered, so now we process __asan_default_options during module startup instead of ASAN startup. This is a change in behavior, but there's no real way around this due to how DLLs are. 4. Build reorganization * I noticed that our current build configuration is very MSVC-specific and so did a bit of reworking. Removed a lot of create_multiple_windows_obj_lib use since it's no longer needed and it changed how we needed to refer to each object_lib by adding runtime configuration to the name, conflicting with how it works for non-MSVC. * No more Win32 static build, use /MD everywhere. * Building with /Zl to avoid defaultlib warnings. In addition: * I've reapplied "[sanitizer][asan][win] Intercept _strdup on Windows instead of strdup" which broke the previous static asan runtime. That runtime is gone now and this change is required for the strdup tests to work. * I've modified the MSVC clang driver to support linking the correct asan libraries, including via defining _DLL (which triggers different defaultlibs and should result in the asan dll thunk being linked, along with the dll CRT (via defaultlib directives). * I've made passing -static-libsan an error on windows, and made -shared-libsan the default. I'm not sure I did this correctly, or in the best way. * Modified the test harnesses to add substitutions for the dynamic and static thunks and to make the library substitutions point to the dynamic asan runtime for all test configurations on windows. Both the static and dynamic windows test configurations remain, because they correspond to the static and dynamic CRT, not the static and dynamic asan runtime library. --------- Co-authored-by: Amy Wishnousky <[email protected]>
1 parent 3cee567 commit 246234a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+1287
-1069
lines changed

clang/lib/Driver/SanitizerArgs.cpp

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -909,10 +909,16 @@ SanitizerArgs::SanitizerArgs(const ToolChain &TC,
909909
DiagnoseErrors);
910910
}
911911

912-
SharedRuntime =
913-
Args.hasFlag(options::OPT_shared_libsan, options::OPT_static_libsan,
914-
TC.getTriple().isAndroid() || TC.getTriple().isOSFuchsia() ||
915-
TC.getTriple().isOSDarwin());
912+
SharedRuntime = Args.hasFlag(
913+
options::OPT_shared_libsan, options::OPT_static_libsan,
914+
TC.getTriple().isAndroid() || TC.getTriple().isOSFuchsia() ||
915+
TC.getTriple().isOSDarwin() || TC.getTriple().isOSWindows());
916+
if (!SharedRuntime && TC.getTriple().isOSWindows()) {
917+
Arg *A =
918+
Args.getLastArg(options::OPT_shared_libsan, options::OPT_static_libsan);
919+
D.Diag(clang::diag::err_drv_unsupported_opt_for_target)
920+
<< A->getSpelling() << TC.getTriple().str();
921+
}
916922

917923
ImplicitCfiRuntime = TC.getTriple().isAndroid();
918924

clang/lib/Driver/ToolChains/MSVC.cpp

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -201,10 +201,10 @@ void visualstudio::Linker::ConstructJob(Compilation &C, const JobAction &JA,
201201
if (TC.getSanitizerArgs(Args).needsAsanRt()) {
202202
CmdArgs.push_back(Args.MakeArgString("-debug"));
203203
CmdArgs.push_back(Args.MakeArgString("-incremental:no"));
204-
if (TC.getSanitizerArgs(Args).needsSharedRt() ||
205-
Args.hasArg(options::OPT__SLASH_MD, options::OPT__SLASH_MDd)) {
206-
for (const auto &Lib : {"asan_dynamic", "asan_dynamic_runtime_thunk"})
207-
CmdArgs.push_back(TC.getCompilerRTArgString(Args, Lib));
204+
CmdArgs.push_back(TC.getCompilerRTArgString(Args, "asan_dynamic"));
205+
auto defines = Args.getAllArgValues(options::OPT_D);
206+
if (Args.hasArg(options::OPT__SLASH_MD, options::OPT__SLASH_MDd) ||
207+
find(begin(defines), end(defines), "_DLL") != end(defines)) {
208208
// Make sure the dynamic runtime thunk is not optimized out at link time
209209
// to ensure proper SEH handling.
210210
CmdArgs.push_back(Args.MakeArgString(
@@ -213,19 +213,15 @@ void visualstudio::Linker::ConstructJob(Compilation &C, const JobAction &JA,
213213
: "-include:__asan_seh_interceptor"));
214214
// Make sure the linker consider all object files from the dynamic runtime
215215
// thunk.
216-
CmdArgs.push_back(Args.MakeArgString(std::string("-wholearchive:") +
216+
CmdArgs.push_back(Args.MakeArgString(
217+
std::string("-wholearchive:") +
217218
TC.getCompilerRT(Args, "asan_dynamic_runtime_thunk")));
218-
} else if (DLL) {
219-
CmdArgs.push_back(TC.getCompilerRTArgString(Args, "asan_dll_thunk"));
220219
} else {
221-
for (const auto &Lib : {"asan", "asan_cxx"}) {
222-
CmdArgs.push_back(TC.getCompilerRTArgString(Args, Lib));
223-
// Make sure the linker consider all object files from the static lib.
224-
// This is necessary because instrumented dlls need access to all the
225-
// interface exported by the static lib in the main executable.
226-
CmdArgs.push_back(Args.MakeArgString(std::string("-wholearchive:") +
227-
TC.getCompilerRT(Args, Lib)));
228-
}
220+
// Make sure the linker consider all object files from the static runtime
221+
// thunk.
222+
CmdArgs.push_back(Args.MakeArgString(
223+
std::string("-wholearchive:") +
224+
TC.getCompilerRT(Args, "asan_static_runtime_thunk")));
229225
}
230226
}
231227

clang/test/Driver/cl-link.c

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,15 @@
1313
// ASAN: link.exe
1414
// ASAN: "-debug"
1515
// ASAN: "-incremental:no"
16-
// ASAN: "{{[^"]*}}clang_rt.asan.lib"
17-
// ASAN: "-wholearchive:{{.*}}clang_rt.asan.lib"
18-
// ASAN: "{{[^"]*}}clang_rt.asan_cxx.lib"
19-
// ASAN: "-wholearchive:{{.*}}clang_rt.asan_cxx.lib"
16+
// ASAN: "{{[^"]*}}clang_rt.asan_dynamic.lib"
17+
// ASAN: "-wholearchive:{{.*}}clang_rt.asan_static_runtime_thunk.lib"
2018
// ASAN: "{{.*}}cl-link{{.*}}.obj"
2119

2220
// RUN: %clang_cl -m32 -arch:IA32 --target=i386-pc-win32 /MD /Tc%s -fuse-ld=link -### -fsanitize=address 2>&1 | FileCheck --check-prefix=ASAN-MD %s
2321
// ASAN-MD: link.exe
2422
// ASAN-MD: "-debug"
2523
// ASAN-MD: "-incremental:no"
2624
// ASAN-MD: "{{.*}}clang_rt.asan_dynamic.lib"
27-
// ASAN-MD: "{{[^"]*}}clang_rt.asan_dynamic_runtime_thunk.lib"
2825
// ASAN-MD: "-include:___asan_seh_interceptor"
2926
// ASAN-MD: "-wholearchive:{{.*}}clang_rt.asan_dynamic_runtime_thunk.lib"
3027
// ASAN-MD: "{{.*}}cl-link{{.*}}.obj"
@@ -40,7 +37,8 @@
4037
// ASAN-DLL: "-dll"
4138
// ASAN-DLL: "-debug"
4239
// ASAN-DLL: "-incremental:no"
43-
// ASAN-DLL: "{{.*}}clang_rt.asan_dll_thunk.lib"
40+
// ASAN-DLL: "{{.*}}clang_rt.asan_dynamic.lib"
41+
// ASAN-DLL: "-wholearchive:{{.*}}clang_rt.asan_static_runtime_thunk.lib"
4442
// ASAN-DLL: "{{.*}}cl-link{{.*}}.obj"
4543

4644
// RUN: %clang_cl /Zi /Tc%s -fuse-ld=link -### 2>&1 | FileCheck --check-prefix=DEBUG %s

compiler-rt/CMakeLists.txt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -378,8 +378,12 @@ if("${COMPILER_RT_DEFAULT_TARGET_ARCH}" MATCHES "s390x")
378378
endif()
379379

380380
if(MSVC)
381-
# FIXME: In fact, sanitizers should support both /MT and /MD, see PR20214.
382-
set(CMAKE_MSVC_RUNTIME_LIBRARY MultiThreaded)
381+
382+
# asan on windows only supports the release dll version of the runtimes, in the interest of
383+
# only having one asan dll to support/test. Having asan statically linked
384+
# with the runtime might be possible, but it multiplies the number of scenerios to test.
385+
# the program USING sanitizers can use whatever version of the runtime it wants to.
386+
set(CMAKE_MSVC_RUNTIME_LIBRARY MultiThreadedDLL)
383387

384388
# Remove any /M[DT][d] flags, and strip any definitions of _DEBUG.
385389
# Since we're using CMAKE_MSVC_RUNTIME_LIBRARY (CMP0091 set to NEW),

compiler-rt/lib/asan/CMakeLists.txt

Lines changed: 82 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,20 @@ set(ASAN_SOURCES
3232
asan_win.cpp
3333
)
3434

35+
if(WIN32)
36+
set(ASAN_DYNAMIC_RUNTIME_THUNK_SOURCES
37+
asan_globals_win.cpp
38+
asan_win_common_runtime_thunk.cpp
39+
asan_win_dynamic_runtime_thunk.cpp
40+
)
41+
set(ASAN_STATIC_RUNTIME_THUNK_SOURCES
42+
asan_globals_win.cpp
43+
asan_malloc_win_thunk.cpp
44+
asan_win_common_runtime_thunk.cpp
45+
asan_win_static_runtime_thunk.cpp
46+
)
47+
endif()
48+
3549
if (NOT WIN32 AND NOT APPLE)
3650
list(APPEND ASAN_SOURCES
3751
asan_interceptors_vfork.S
@@ -136,7 +150,7 @@ append_list_if(MINGW "${MINGW_LIBRARIES}" ASAN_DYNAMIC_LIBS)
136150
add_compiler_rt_object_libraries(RTAsan_dynamic
137151
OS ${SANITIZER_COMMON_SUPPORTED_OS}
138152
ARCHS ${ASAN_SUPPORTED_ARCH}
139-
SOURCES ${ASAN_SOURCES} ${ASAN_CXX_SOURCES}
153+
SOURCES ${ASAN_SOURCES}
140154
ADDITIONAL_HEADERS ${ASAN_HEADERS}
141155
CFLAGS ${ASAN_DYNAMIC_CFLAGS}
142156
DEFS ${ASAN_DYNAMIC_DEFINITIONS})
@@ -221,46 +235,52 @@ else()
221235
RTSanitizerCommonSymbolizerInternal
222236
RTLSanCommon
223237
RTUbsan)
238+
if (NOT WIN32)
239+
add_compiler_rt_runtime(clang_rt.asan
240+
STATIC
241+
ARCHS ${ASAN_SUPPORTED_ARCH}
242+
OBJECT_LIBS RTAsan_preinit
243+
RTAsan
244+
${ASAN_COMMON_RUNTIME_OBJECT_LIBS}
245+
CFLAGS ${ASAN_CFLAGS}
246+
DEFS ${ASAN_COMMON_DEFINITIONS}
247+
PARENT_TARGET asan)
224248

225-
add_compiler_rt_runtime(clang_rt.asan
226-
STATIC
227-
ARCHS ${ASAN_SUPPORTED_ARCH}
228-
OBJECT_LIBS RTAsan_preinit
229-
RTAsan
230-
${ASAN_COMMON_RUNTIME_OBJECT_LIBS}
231-
CFLAGS ${ASAN_CFLAGS}
232-
DEFS ${ASAN_COMMON_DEFINITIONS}
233-
PARENT_TARGET asan)
234-
235-
add_compiler_rt_runtime(clang_rt.asan_cxx
236-
STATIC
237-
ARCHS ${ASAN_SUPPORTED_ARCH}
238-
OBJECT_LIBS RTAsan_cxx
239-
RTUbsan_cxx
240-
CFLAGS ${ASAN_CFLAGS}
241-
DEFS ${ASAN_COMMON_DEFINITIONS}
242-
PARENT_TARGET asan)
249+
add_compiler_rt_runtime(clang_rt.asan_cxx
250+
STATIC
251+
ARCHS ${ASAN_SUPPORTED_ARCH}
252+
OBJECT_LIBS RTAsan_cxx
253+
RTUbsan_cxx
254+
CFLAGS ${ASAN_CFLAGS}
255+
DEFS ${ASAN_COMMON_DEFINITIONS}
256+
PARENT_TARGET asan)
243257

244-
add_compiler_rt_runtime(clang_rt.asan_static
245-
STATIC
246-
ARCHS ${ASAN_SUPPORTED_ARCH}
247-
OBJECT_LIBS RTAsan_static
248-
CFLAGS ${ASAN_CFLAGS}
249-
DEFS ${ASAN_COMMON_DEFINITIONS}
250-
PARENT_TARGET asan)
258+
add_compiler_rt_runtime(clang_rt.asan_static
259+
STATIC
260+
ARCHS ${ASAN_SUPPORTED_ARCH}
261+
OBJECT_LIBS RTAsan_static
262+
CFLAGS ${ASAN_CFLAGS}
263+
DEFS ${ASAN_COMMON_DEFINITIONS}
264+
PARENT_TARGET asan)
251265

252-
add_compiler_rt_runtime(clang_rt.asan-preinit
253-
STATIC
254-
ARCHS ${ASAN_SUPPORTED_ARCH}
255-
OBJECT_LIBS RTAsan_preinit
256-
CFLAGS ${ASAN_CFLAGS}
257-
DEFS ${ASAN_COMMON_DEFINITIONS}
258-
PARENT_TARGET asan)
266+
add_compiler_rt_runtime(clang_rt.asan-preinit
267+
STATIC
268+
ARCHS ${ASAN_SUPPORTED_ARCH}
269+
OBJECT_LIBS RTAsan_preinit
270+
CFLAGS ${ASAN_CFLAGS}
271+
DEFS ${ASAN_COMMON_DEFINITIONS}
272+
PARENT_TARGET asan)
273+
endif()
259274

260275
foreach(arch ${ASAN_SUPPORTED_ARCH})
261276
if (COMPILER_RT_HAS_VERSION_SCRIPT)
277+
if(WIN32)
278+
set(SANITIZER_RT_VERSION_LIST_LIBS clang_rt.asan-${arch})
279+
else()
280+
set(SANITIZER_RT_VERSION_LIST_LIBS clang_rt.asan-${arch} clang_rt.asan_cxx-${arch})
281+
endif()
262282
add_sanitizer_rt_version_list(clang_rt.asan-dynamic-${arch}
263-
LIBS clang_rt.asan-${arch} clang_rt.asan_cxx-${arch}
283+
LIBS ${SANITIZER_RT_VERSION_LIST_LIBS}
264284
EXTRA asan.syms.extra)
265285
set(VERSION_SCRIPT_FLAG
266286
-Wl,--version-script,${CMAKE_CURRENT_BINARY_DIR}/clang_rt.asan-dynamic-${arch}.vers)
@@ -278,25 +298,11 @@ else()
278298
endif()
279299

280300
set(ASAN_DYNAMIC_WEAK_INTERCEPTION)
281-
if (WIN32)
282-
add_compiler_rt_object_libraries(AsanWeakInterception
283-
${SANITIZER_COMMON_SUPPORTED_OS}
284-
ARCHS ${arch}
285-
SOURCES
286-
asan_win_weak_interception.cpp
287-
CFLAGS ${ASAN_CFLAGS} -DSANITIZER_DYNAMIC
288-
DEFS ${ASAN_COMMON_DEFINITIONS})
289-
set(ASAN_DYNAMIC_WEAK_INTERCEPTION
290-
AsanWeakInterception
291-
UbsanWeakInterception
292-
SancovWeakInterception
293-
SanitizerCommonWeakInterception)
294-
endif()
295-
296301
add_compiler_rt_runtime(clang_rt.asan
297302
SHARED
298303
ARCHS ${arch}
299304
OBJECT_LIBS ${ASAN_COMMON_RUNTIME_OBJECT_LIBS}
305+
RTAsan_cxx
300306
RTAsan_dynamic
301307
# The only purpose of RTAsan_dynamic_version_script_dummy is to
302308
# carry a dependency of the shared runtime on the version script.
@@ -324,49 +330,48 @@ else()
324330
endif()
325331

326332
if (WIN32)
327-
add_compiler_rt_object_libraries(AsanDllThunk
328-
${SANITIZER_COMMON_SUPPORTED_OS}
329-
ARCHS ${arch}
330-
SOURCES asan_globals_win.cpp
331-
asan_win_dll_thunk.cpp
332-
CFLAGS ${ASAN_CFLAGS} -DSANITIZER_DLL_THUNK
333-
DEFS ${ASAN_COMMON_DEFINITIONS})
334-
335-
add_compiler_rt_runtime(clang_rt.asan_dll_thunk
336-
STATIC
337-
ARCHS ${arch}
338-
OBJECT_LIBS AsanDllThunk
339-
UbsanDllThunk
340-
SancovDllThunk
341-
SanitizerCommonDllThunk
342-
SOURCES $<TARGET_OBJECTS:RTInterception.${arch}>
343-
PARENT_TARGET asan)
344-
345333
set(DYNAMIC_RUNTIME_THUNK_CFLAGS "-DSANITIZER_DYNAMIC_RUNTIME_THUNK")
346-
if(MSVC)
347-
list(APPEND DYNAMIC_RUNTIME_THUNK_CFLAGS "-Zl")
348-
elseif(CMAKE_C_COMPILER_ID MATCHES Clang)
349-
list(APPEND DYNAMIC_RUNTIME_THUNK_CFLAGS "-nodefaultlibs")
350-
endif()
351334

352335
add_compiler_rt_object_libraries(AsanDynamicRuntimeThunk
353336
${SANITIZER_COMMON_SUPPORTED_OS}
354337
ARCHS ${arch}
355-
SOURCES asan_globals_win.cpp
356-
asan_win_dynamic_runtime_thunk.cpp
338+
SOURCES ${ASAN_DYNAMIC_RUNTIME_THUNK_SOURCES}
357339
CFLAGS ${ASAN_CFLAGS} ${DYNAMIC_RUNTIME_THUNK_CFLAGS}
358340
DEFS ${ASAN_COMMON_DEFINITIONS})
359341

360342
add_compiler_rt_runtime(clang_rt.asan_dynamic_runtime_thunk
361343
STATIC
362344
ARCHS ${arch}
363345
OBJECT_LIBS AsanDynamicRuntimeThunk
364-
UbsanDynamicRuntimeThunk
365-
SancovDynamicRuntimeThunk
366-
SanitizerCommonDynamicRuntimeThunk
346+
UbsanRuntimeThunk
347+
SancovRuntimeThunk
348+
SanitizerRuntimeThunk
367349
CFLAGS ${ASAN_CFLAGS} ${DYNAMIC_RUNTIME_THUNK_CFLAGS}
368350
DEFS ${ASAN_COMMON_DEFINITIONS}
369351
PARENT_TARGET asan)
352+
353+
# mingw does not support static linkage of the CRT
354+
if(NOT MINGW)
355+
set(STATIC_RUNTIME_THUNK_CFLAGS "-DSANITIZER_STATIC_RUNTIME_THUNK")
356+
357+
add_compiler_rt_object_libraries(AsanStaticRuntimeThunk
358+
${SANITIZER_COMMON_SUPPORTED_OS}
359+
ARCHS ${arch}
360+
SOURCES ${ASAN_STATIC_RUNTIME_THUNK_SOURCES}
361+
CFLAGS ${ASAN_DYNAMIC_CFLAGS} ${STATIC_RUNTIME_THUNK_CFLAGS}
362+
DEFS ${ASAN_DYNAMIC_DEFINITIONS})
363+
364+
add_compiler_rt_runtime(clang_rt.asan_static_runtime_thunk
365+
STATIC
366+
ARCHS ${arch}
367+
OBJECT_LIBS AsanStaticRuntimeThunk
368+
UbsanRuntimeThunk
369+
SancovRuntimeThunk
370+
SanitizerRuntimeThunk
371+
CFLAGS ${ASAN_DYNAMIC_CFLAGS} ${STATIC_RUNTIME_THUNK_CFLAGS}
372+
DEFS ${ASAN_DYNAMIC_DEFINITIONS}
373+
PARENT_TARGET asan)
374+
endif()
370375
endif()
371376
endforeach()
372377
endif()

0 commit comments

Comments
 (0)