diff options
author | Kuba Brecka <kuba.brecka@gmail.com> | 2014-09-04 01:03:18 +0000 |
---|---|---|
committer | Kuba Brecka <kuba.brecka@gmail.com> | 2014-09-04 01:03:18 +0000 |
commit | beed821ffb058c164a885f96f2308c71cc7b632c (patch) | |
tree | 934e92deb6a97470ddbe389447d1daa20f4bc719 /lldb/source | |
parent | 13046deef35374954dcbd164ba2f56f3166648e6 (diff) | |
download | bcm5719-llvm-beed821ffb058c164a885f96f2308c71cc7b632c.tar.gz bcm5719-llvm-beed821ffb058c164a885f96f2308c71cc7b632c.zip |
ASan malloc/free history threads
Reviewed at http://reviews.llvm.org/D4596
llvm-svn: 217116
Diffstat (limited to 'lldb/source')
-rw-r--r-- | lldb/source/CMakeLists.txt | 1 | ||||
-rw-r--r-- | lldb/source/Commands/CommandObjectMemory.cpp | 93 | ||||
-rw-r--r-- | lldb/source/Core/PluginManager.cpp | 104 | ||||
-rw-r--r-- | lldb/source/Plugins/CMakeLists.txt | 1 | ||||
-rw-r--r-- | lldb/source/Plugins/Makefile | 3 | ||||
-rw-r--r-- | lldb/source/Plugins/MemoryHistory/CMakeLists.txt | 1 | ||||
-rw-r--r-- | lldb/source/Plugins/MemoryHistory/asan/CMakeLists.txt | 5 | ||||
-rw-r--r-- | lldb/source/Plugins/MemoryHistory/asan/Makefile | 14 | ||||
-rw-r--r-- | lldb/source/Plugins/MemoryHistory/asan/MemoryHistoryASan.cpp | 185 | ||||
-rw-r--r-- | lldb/source/Plugins/MemoryHistory/asan/MemoryHistoryASan.h | 62 | ||||
-rw-r--r-- | lldb/source/Plugins/Process/Utility/HistoryThread.h | 12 | ||||
-rw-r--r-- | lldb/source/Target/CMakeLists.txt | 1 | ||||
-rw-r--r-- | lldb/source/Target/MemoryHistory.cpp | 28 | ||||
-rw-r--r-- | lldb/source/lldb.cpp | 3 |
14 files changed, 512 insertions, 1 deletions
diff --git a/lldb/source/CMakeLists.txt b/lldb/source/CMakeLists.txt index a1ee299f21a..1531a4af396 100644 --- a/lldb/source/CMakeLists.txt +++ b/lldb/source/CMakeLists.txt @@ -84,6 +84,7 @@ set( LLDB_USED_LIBS lldbPluginInstructionARM64 lldbPluginObjectFilePECOFF lldbPluginOSPython + lldbPluginMemoryHistoryASan ) # Need to export the API in the liblldb.dll for Windows diff --git a/lldb/source/Commands/CommandObjectMemory.cpp b/lldb/source/Commands/CommandObjectMemory.cpp index bfbb296158a..8144f48a5b4 100644 --- a/lldb/source/Commands/CommandObjectMemory.cpp +++ b/lldb/source/Commands/CommandObjectMemory.cpp @@ -33,8 +33,10 @@ #include "lldb/Interpreter/OptionGroupValueObjectDisplay.h" #include "lldb/Interpreter/OptionValueString.h" #include "lldb/Symbol/TypeList.h" +#include "lldb/Target/MemoryHistory.h" #include "lldb/Target/Process.h" #include "lldb/Target/StackFrame.h" +#include "lldb/Target/Thread.h" using namespace lldb; using namespace lldb_private; @@ -1667,6 +1669,96 @@ protected: OptionGroupWriteMemory m_memory_options; }; +//---------------------------------------------------------------------- +// Get malloc/free history of a memory address. +//---------------------------------------------------------------------- +class CommandObjectMemoryHistory : public CommandObjectParsed +{ +public: + + CommandObjectMemoryHistory (CommandInterpreter &interpreter) : + CommandObjectParsed (interpreter, + "memory history", + "Prints out the recorded stack traces for allocation/deallocation of a memory address.", + NULL, + eFlagRequiresTarget | eFlagRequiresProcess | eFlagProcessMustBePaused | eFlagProcessMustBeLaunched) + { + CommandArgumentEntry arg1; + CommandArgumentData addr_arg; + + // Define the first (and only) variant of this arg. + addr_arg.arg_type = eArgTypeAddress; + addr_arg.arg_repetition = eArgRepeatPlain; + + // There is only one variant this argument could be; put it into the argument entry. + arg1.push_back (addr_arg); + + // Push the data for the first argument into the m_arguments vector. + m_arguments.push_back (arg1); + } + + virtual + ~CommandObjectMemoryHistory () + { + } + + virtual const char *GetRepeatCommand (Args ¤t_command_args, uint32_t index) + { + return m_cmd_name.c_str(); + } + +protected: + virtual bool + DoExecute (Args& command, CommandReturnObject &result) + { + const size_t argc = command.GetArgumentCount(); + + if (argc == 0 || argc > 1) + { + result.AppendErrorWithFormat ("%s takes an address expression", m_cmd_name.c_str()); + result.SetStatus(eReturnStatusFailed); + return false; + } + + Error error; + lldb::addr_t addr = Args::StringToAddress (&m_exe_ctx, + command.GetArgumentAtIndex(0), + LLDB_INVALID_ADDRESS, + &error); + + if (addr == LLDB_INVALID_ADDRESS) + { + result.AppendError("invalid address expression"); + result.AppendError(error.AsCString()); + result.SetStatus(eReturnStatusFailed); + return false; + } + + Stream *output_stream = &result.GetOutputStream(); + + const ProcessSP &process_sp = m_exe_ctx.GetProcessSP(); + const MemoryHistorySP &memory_history = MemoryHistory::FindPlugin(process_sp); + + if (! memory_history.get()) + { + result.AppendError("no available memory history provider"); + result.SetStatus(eReturnStatusFailed); + return false; + } + + HistoryThreads thread_list = memory_history->GetHistoryThreads(addr); + + for (auto thread : thread_list) { + thread->GetStatus(*output_stream, 0, UINT32_MAX, 0); + } + + result.SetStatus(eReturnStatusSuccessFinishResult); + + return true; + } + +}; + //------------------------------------------------------------------------- // CommandObjectMemory @@ -1681,6 +1773,7 @@ CommandObjectMemory::CommandObjectMemory (CommandInterpreter &interpreter) : LoadSubCommand ("find", CommandObjectSP (new CommandObjectMemoryFind (interpreter))); LoadSubCommand ("read", CommandObjectSP (new CommandObjectMemoryRead (interpreter))); LoadSubCommand ("write", CommandObjectSP (new CommandObjectMemoryWrite (interpreter))); + LoadSubCommand ("history", CommandObjectSP (new CommandObjectMemoryHistory (interpreter))); } CommandObjectMemory::~CommandObjectMemory () diff --git a/lldb/source/Core/PluginManager.cpp b/lldb/source/Core/PluginManager.cpp index cda55802fab..0cca76ee95e 100644 --- a/lldb/source/Core/PluginManager.cpp +++ b/lldb/source/Core/PluginManager.cpp @@ -2068,6 +2068,110 @@ PluginManager::GetUnwindAssemblyCreateCallbackForPluginName (const ConstString & return NULL; } +#pragma mark MemoryHistory + +struct MemoryHistoryInstance +{ + MemoryHistoryInstance() : + name(), + description(), + create_callback(NULL) + { + } + + ConstString name; + std::string description; + MemoryHistoryCreateInstance create_callback; +}; + +typedef std::vector<MemoryHistoryInstance> MemoryHistoryInstances; + +static Mutex & +GetMemoryHistoryMutex () +{ + static Mutex g_instances_mutex (Mutex::eMutexTypeRecursive); + return g_instances_mutex; +} + +static MemoryHistoryInstances & +GetMemoryHistoryInstances () +{ + static MemoryHistoryInstances g_instances; + return g_instances; +} + +bool +PluginManager::RegisterPlugin +( + const ConstString &name, + const char *description, + MemoryHistoryCreateInstance create_callback + ) +{ + if (create_callback) + { + MemoryHistoryInstance instance; + assert ((bool)name); + instance.name = name; + if (description && description[0]) + instance.description = description; + instance.create_callback = create_callback; + Mutex::Locker locker (GetMemoryHistoryMutex ()); + GetMemoryHistoryInstances ().push_back (instance); + } + return false; +} + +bool +PluginManager::UnregisterPlugin (MemoryHistoryCreateInstance create_callback) +{ + if (create_callback) + { + Mutex::Locker locker (GetMemoryHistoryMutex ()); + MemoryHistoryInstances &instances = GetMemoryHistoryInstances (); + + MemoryHistoryInstances::iterator pos, end = instances.end(); + for (pos = instances.begin(); pos != end; ++ pos) + { + if (pos->create_callback == create_callback) + { + instances.erase(pos); + return true; + } + } + } + return false; +} + +MemoryHistoryCreateInstance +PluginManager::GetMemoryHistoryCreateCallbackAtIndex (uint32_t idx) +{ + Mutex::Locker locker (GetMemoryHistoryMutex ()); + MemoryHistoryInstances &instances = GetMemoryHistoryInstances (); + if (idx < instances.size()) + return instances[idx].create_callback; + return NULL; +} + + +MemoryHistoryCreateInstance +PluginManager::GetMemoryHistoryCreateCallbackForPluginName (const ConstString &name) +{ + if (name) + { + Mutex::Locker locker (GetMemoryHistoryMutex ()); + MemoryHistoryInstances &instances = GetMemoryHistoryInstances (); + + MemoryHistoryInstances::iterator pos, end = instances.end(); + for (pos = instances.begin(); pos != end; ++ pos) + { + if (name == pos->name) + return pos->create_callback; + } + } + return NULL; +} + void PluginManager::DebuggerInitialize (Debugger &debugger) { diff --git a/lldb/source/Plugins/CMakeLists.txt b/lldb/source/Plugins/CMakeLists.txt index 6b409ef4f97..b55936fc5af 100644 --- a/lldb/source/Plugins/CMakeLists.txt +++ b/lldb/source/Plugins/CMakeLists.txt @@ -4,6 +4,7 @@ add_subdirectory(DynamicLoader) add_subdirectory(Instruction) add_subdirectory(JITLoader) add_subdirectory(LanguageRuntime) +add_subdirectory(MemoryHistory) add_subdirectory(ObjectContainer) add_subdirectory(ObjectFile) add_subdirectory(OperatingSystem) diff --git a/lldb/source/Plugins/Makefile b/lldb/source/Plugins/Makefile index 2427aea29a1..514ed3b6aa4 100644 --- a/lldb/source/Plugins/Makefile +++ b/lldb/source/Plugins/Makefile @@ -24,7 +24,8 @@ DIRS := ABI/MacOSX-arm ABI/MacOSX-arm64 ABI/MacOSX-i386 ABI/SysV-x86_64 ABI/SysV DynamicLoader/POSIX-DYLD \ DynamicLoader/Hexagon-DYLD \ OperatingSystem/Python \ - SymbolVendor/ELF + SymbolVendor/ELF \ + MemoryHistory/asan ifeq ($(HOST_OS),Darwin) DIRS += Process/MacOSX-Kernel diff --git a/lldb/source/Plugins/MemoryHistory/CMakeLists.txt b/lldb/source/Plugins/MemoryHistory/CMakeLists.txt new file mode 100644 index 00000000000..113f0636257 --- /dev/null +++ b/lldb/source/Plugins/MemoryHistory/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(asan) diff --git a/lldb/source/Plugins/MemoryHistory/asan/CMakeLists.txt b/lldb/source/Plugins/MemoryHistory/asan/CMakeLists.txt new file mode 100644 index 00000000000..442a538d414 --- /dev/null +++ b/lldb/source/Plugins/MemoryHistory/asan/CMakeLists.txt @@ -0,0 +1,5 @@ +set(LLVM_NO_RTTI 1) + +add_lldb_library(lldbPluginMemoryHistoryASan + MemoryHistoryASan.cpp + ) diff --git a/lldb/source/Plugins/MemoryHistory/asan/Makefile b/lldb/source/Plugins/MemoryHistory/asan/Makefile new file mode 100644 index 00000000000..86de6aba363 --- /dev/null +++ b/lldb/source/Plugins/MemoryHistory/asan/Makefile @@ -0,0 +1,14 @@ +##===- source/Plugins/MemoryHistory/asan/Makefile -------------*- Makefile -*-===## +# +# The LLVM Compiler Infrastructure +# +# This file is distributed under the University of Illinois Open Source +# License. See LICENSE.TXT for details. +# +##===----------------------------------------------------------------------===## + +LLDB_LEVEL := ../../../.. +LIBRARYNAME := lldbPluginMemoryHistoryASan +BUILD_ARCHIVE = 1 + +include $(LLDB_LEVEL)/Makefile diff --git a/lldb/source/Plugins/MemoryHistory/asan/MemoryHistoryASan.cpp b/lldb/source/Plugins/MemoryHistory/asan/MemoryHistoryASan.cpp new file mode 100644 index 00000000000..0c0e6ab26e6 --- /dev/null +++ b/lldb/source/Plugins/MemoryHistory/asan/MemoryHistoryASan.cpp @@ -0,0 +1,185 @@ +//===-- MemoryHistoryASan.cpp -----------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "MemoryHistoryASan.h" + +#include "lldb/Target/MemoryHistory.h" + +#include "lldb/lldb-private.h" +#include "lldb/Core/PluginInterface.h" +#include "lldb/Core/PluginManager.h" +#include "lldb/Target/ThreadList.h" +#include "lldb/Target/ExecutionContext.h" +#include "lldb/Target/Target.h" +#include "lldb/Target/Thread.h" +#include "lldb/Core/Module.h" +#include "Plugins/Process/Utility/HistoryThread.h" +#include "lldb/Core/ValueObject.h" + +using namespace lldb; +using namespace lldb_private; + +MemoryHistorySP +MemoryHistoryASan::CreateInstance (const ProcessSP &process_sp) +{ + if (!process_sp.get()) + return NULL; + + Target & target = process_sp->GetTarget(); + + bool found_asan_runtime = false; + + const ModuleList &target_modules = target.GetImages(); + Mutex::Locker modules_locker(target_modules.GetMutex()); + const size_t num_modules = target_modules.GetSize(); + for (size_t i = 0; i < num_modules; ++i) + { + Module *module_pointer = target_modules.GetModulePointerAtIndexUnlocked(i); + + SymbolContextList sc_list; + const bool include_symbols = true; + const bool append = true; + const bool include_inlines = true; + + size_t num_matches = module_pointer->FindFunctions(ConstString("__asan_get_alloc_stack"), NULL, eFunctionNameTypeAuto, include_symbols, include_inlines, append, sc_list); + + if (num_matches) + { + found_asan_runtime = true; + break; + } + } + + if (! found_asan_runtime) + return MemoryHistorySP(); + + return MemoryHistorySP(new MemoryHistoryASan(process_sp)); +} + +void +MemoryHistoryASan::Initialize() +{ + PluginManager::RegisterPlugin (GetPluginNameStatic(), + "ASan memory history provider.", + CreateInstance); +} + +void +MemoryHistoryASan::Terminate() +{ + PluginManager::UnregisterPlugin (CreateInstance); +} + + +ConstString +MemoryHistoryASan::GetPluginNameStatic() +{ + static ConstString g_name("asan"); + return g_name; +} + +MemoryHistoryASan::MemoryHistoryASan(const ProcessSP &process_sp) +{ + this->m_process_sp = process_sp; +} + +const char * +memory_history_asan_command_format = R"( + struct t { + void *alloc_trace[256]; + size_t alloc_count; + int alloc_tid; + + void *free_trace[256]; + size_t free_count; + int free_tid; + } t; + + t.alloc_count = ((size_t (*) (void *, void **, size_t, int *))__asan_get_alloc_stack)((void *)0x%)" PRIx64 R"(, t.alloc_trace, 256, &t.alloc_tid); + t.free_count = ((size_t (*) (void *, void **, size_t, int *))__asan_get_free_stack)((void *)0x%)" PRIx64 R"(, t.free_trace, 256, &t.free_tid); + + t; +)"; + +#define GET_STACK_FUNCTION_TIMEOUT_USEC 2*1000*1000 + +HistoryThreads +MemoryHistoryASan::GetHistoryThreads(lldb::addr_t address) +{ + ProcessSP process_sp = m_process_sp; + ThreadSP thread_sp = m_process_sp->GetThreadList().GetSelectedThread(); + StackFrameSP frame_sp = thread_sp->GetSelectedFrame(); + + if (!frame_sp) + { + return HistoryThreads(); + } + + ExecutionContext exe_ctx (frame_sp); + ValueObjectSP return_value_sp; + StreamString expr; + expr.Printf(memory_history_asan_command_format, address, address); + + EvaluateExpressionOptions options; + options.SetUnwindOnError(true); + options.SetTryAllThreads(true); + options.SetStopOthers(true); + options.SetIgnoreBreakpoints(true); + options.SetTimeoutUsec(GET_STACK_FUNCTION_TIMEOUT_USEC); + + if (m_process_sp->GetTarget().EvaluateExpression(expr.GetData(), frame_sp.get(), return_value_sp, options) != eExpressionCompleted) + { + return HistoryThreads(); + } + if (!return_value_sp) + { + return HistoryThreads(); + } + + HistoryThreads result; + + int alloc_count = return_value_sp->GetValueForExpressionPath(".alloc_count")->GetValueAsUnsigned(0); + int free_count = return_value_sp->GetValueForExpressionPath(".free_count")->GetValueAsUnsigned(0); + tid_t alloc_tid = return_value_sp->GetValueForExpressionPath(".alloc_tid")->GetValueAsUnsigned(0); + tid_t free_tid = return_value_sp->GetValueForExpressionPath(".free_tid")->GetValueAsUnsigned(0); + + if (alloc_count > 0) + { + std::vector<lldb::addr_t> pcs; + ValueObjectSP trace_sp = return_value_sp->GetValueForExpressionPath(".alloc_trace"); + for (int i = 0; i < alloc_count; i++) { + addr_t pc = trace_sp->GetChildAtIndex(i, true)->GetValueAsUnsigned(0); + pcs.push_back(pc); + } + + HistoryThread *history_thread = new HistoryThread(*process_sp, alloc_tid, pcs, 0, false); + ThreadSP new_thread_sp(history_thread); + // let's use thread name for the type of history thread, since history threads don't have names anyway + history_thread->SetThreadName("Memory allocated at"); + result.push_back(new_thread_sp); + } + + if (free_count > 0) + { + std::vector<lldb::addr_t> pcs; + ValueObjectSP trace_sp = return_value_sp->GetValueForExpressionPath(".free_trace"); + for (int i = 0; i < free_count; i++) { + addr_t pc = trace_sp->GetChildAtIndex(i, true)->GetValueAsUnsigned(0); + pcs.push_back(pc); + } + + HistoryThread *history_thread = new HistoryThread(*process_sp, free_tid, pcs, 0, false); + ThreadSP new_thread_sp(history_thread); + // let's use thread name for the type of history thread, since history threads don't have names anyway + history_thread->SetThreadName("Memory deallocated at"); + result.push_back(new_thread_sp); + } + + return result; +} diff --git a/lldb/source/Plugins/MemoryHistory/asan/MemoryHistoryASan.h b/lldb/source/Plugins/MemoryHistory/asan/MemoryHistoryASan.h new file mode 100644 index 00000000000..5307e0b3408 --- /dev/null +++ b/lldb/source/Plugins/MemoryHistory/asan/MemoryHistoryASan.h @@ -0,0 +1,62 @@ +//===-- MemoryHistoryASan.h ----------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef liblldb_MemoryHistoryASan_h_ +#define liblldb_MemoryHistoryASan_h_ + +// C Includes +// C++ Includes +// Other libraries and framework includes +// Project includes +#include "lldb/lldb-private.h" +#include "lldb/Target/ABI.h" +#include "lldb/Target/MemoryHistory.h" +#include "lldb/Target/Process.h" + +namespace lldb_private { + +class MemoryHistoryASan : public lldb_private::MemoryHistory +{ +public: + + static lldb::MemoryHistorySP + CreateInstance (const lldb::ProcessSP &process_sp); + + static void + Initialize(); + + static void + Terminate(); + + static lldb_private::ConstString + GetPluginNameStatic(); + + virtual + ~MemoryHistoryASan () {} + + virtual lldb_private::ConstString + GetPluginName() { return GetPluginNameStatic(); } + + virtual uint32_t + GetPluginVersion() { return 1; } + + virtual lldb_private::HistoryThreads + GetHistoryThreads(lldb::addr_t address); + +private: + + MemoryHistoryASan(const lldb::ProcessSP &process_sp); + + lldb::ProcessSP m_process_sp; + +}; + +} // namespace lldb_private + +#endif // liblldb_MemoryHistoryASan_h_ diff --git a/lldb/source/Plugins/Process/Utility/HistoryThread.h b/lldb/source/Plugins/Process/Utility/HistoryThread.h index f9a431d8340..51173c626d7 100644 --- a/lldb/source/Plugins/Process/Utility/HistoryThread.h +++ b/lldb/source/Plugins/Process/Utility/HistoryThread.h @@ -101,6 +101,18 @@ public: { m_thread_name = name; } + + virtual const char * + GetName () + { + return m_thread_name.c_str(); + } + + virtual void + SetName(const char *name) + { + m_thread_name = name; + } protected: virtual lldb::StackFrameListSP diff --git a/lldb/source/Target/CMakeLists.txt b/lldb/source/Target/CMakeLists.txt index c7b22e05bd5..3dc3617d8ea 100644 --- a/lldb/source/Target/CMakeLists.txt +++ b/lldb/source/Target/CMakeLists.txt @@ -11,6 +11,7 @@ add_lldb_library(lldbTarget JITLoaderList.cpp LanguageRuntime.cpp Memory.cpp + MemoryHistory.cpp NativeRegisterContext.cpp NativeRegisterContextRegisterInfo.cpp ObjCLanguageRuntime.cpp diff --git a/lldb/source/Target/MemoryHistory.cpp b/lldb/source/Target/MemoryHistory.cpp new file mode 100644 index 00000000000..53d675ab09e --- /dev/null +++ b/lldb/source/Target/MemoryHistory.cpp @@ -0,0 +1,28 @@ +//===-- MemoryHistory.cpp -------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "lldb/Target/MemoryHistory.h" + +#include "lldb/Core/PluginManager.h" + +using namespace lldb; +using namespace lldb_private; + +lldb::MemoryHistorySP +MemoryHistory::FindPlugin (const ProcessSP process) +{ + MemoryHistoryCreateInstance create_callback = NULL; + + for (uint32_t idx = 0; (create_callback = PluginManager::GetMemoryHistoryCreateCallbackAtIndex(idx)) != NULL; ++idx) + { + return create_callback(process); + } + + return MemoryHistorySP(); +} diff --git a/lldb/source/lldb.cpp b/lldb/source/lldb.cpp index cd620b7945b..cb2af8a081d 100644 --- a/lldb/source/lldb.cpp +++ b/lldb/source/lldb.cpp @@ -92,6 +92,7 @@ #include "Plugins/Platform/gdb-server/PlatformRemoteGDBServer.h" #include "Plugins/Process/gdb-remote/ProcessGDBRemote.h" #include "Plugins/DynamicLoader/Static/DynamicLoaderStatic.h" +#include "Plugins/MemoryHistory/asan/MemoryHistoryASan.h" using namespace lldb; using namespace lldb_private; @@ -154,6 +155,7 @@ lldb_private::Initialize () #endif JITLoaderGDB::Initialize(); ProcessElfCore::Initialize(); + MemoryHistoryASan::Initialize(); #if defined (__APPLE__) //---------------------------------------------------------------------- @@ -244,6 +246,7 @@ lldb_private::Terminate () #endif JITLoaderGDB::Terminate(); ProcessElfCore::Terminate(); + MemoryHistoryASan::Terminate(); #if defined (__APPLE__) DynamicLoaderMacOSXDYLD::Terminate(); |