CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
471 lines
17 KiB
C++
471 lines
17 KiB
C++
//===-- ScriptedThread.cpp ------------------------------------------------===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "ScriptedThread.h"
|
|
#include "ScriptedFrame.h"
|
|
|
|
#include "Plugins/Process/Utility/RegisterContextThreadMemory.h"
|
|
#include "Plugins/Process/Utility/StopInfoMachException.h"
|
|
#include "lldb/Target/OperatingSystem.h"
|
|
#include "lldb/Target/Process.h"
|
|
#include "lldb/Target/RegisterContext.h"
|
|
#include "lldb/Target/StopInfo.h"
|
|
#include "lldb/Target/Unwind.h"
|
|
#include "lldb/Utility/DataBufferHeap.h"
|
|
#include "lldb/Utility/LLDBLog.h"
|
|
#include <memory>
|
|
#include <optional>
|
|
|
|
using namespace lldb;
|
|
using namespace lldb_private;
|
|
|
|
void ScriptedThread::CheckInterpreterAndScriptObject() const {
|
|
lldbassert(m_script_object_sp && "Invalid Script Object.");
|
|
lldbassert(GetInterface() && "Invalid Scripted Thread Interface.");
|
|
}
|
|
|
|
llvm::Expected<std::shared_ptr<ScriptedThread>>
|
|
ScriptedThread::Create(ScriptedProcess &process,
|
|
StructuredData::Generic *script_object) {
|
|
if (!process.IsValid())
|
|
return llvm::createStringError(llvm::inconvertibleErrorCode(),
|
|
"Invalid scripted process.");
|
|
|
|
process.CheckScriptedInterface();
|
|
|
|
auto scripted_thread_interface =
|
|
process.GetInterface().CreateScriptedThreadInterface();
|
|
if (!scripted_thread_interface)
|
|
return llvm::createStringError(
|
|
llvm::inconvertibleErrorCode(),
|
|
"Failed to create scripted thread interface.");
|
|
|
|
llvm::StringRef thread_class_name;
|
|
if (!script_object) {
|
|
std::optional<std::string> class_name =
|
|
process.GetInterface().GetScriptedThreadPluginName();
|
|
if (!class_name || class_name->empty())
|
|
return llvm::createStringError(
|
|
llvm::inconvertibleErrorCode(),
|
|
"Failed to get scripted thread class name.");
|
|
thread_class_name = *class_name;
|
|
}
|
|
|
|
ExecutionContext exe_ctx(process);
|
|
// The legacy thread-spawn path (no script_object) needs to instantiate a
|
|
// *thread* Python class whose name comes from the process plugin, not the
|
|
// process's own class name. Build a thread-specific metadata for that case;
|
|
// when script_object is non-null the class name is unused so we just forward
|
|
// the process's metadata.
|
|
ScriptedMetadata thread_metadata =
|
|
script_object ? process.m_scripted_metadata
|
|
: ScriptedMetadata(thread_class_name,
|
|
process.m_scripted_metadata.GetArgsSP());
|
|
auto obj_or_err = scripted_thread_interface->CreatePluginObject(
|
|
thread_metadata, exe_ctx, script_object);
|
|
|
|
if (!obj_or_err) {
|
|
llvm::consumeError(obj_or_err.takeError());
|
|
return llvm::createStringError(llvm::inconvertibleErrorCode(),
|
|
"Failed to create script object.");
|
|
}
|
|
|
|
StructuredData::GenericSP owned_script_object_sp = *obj_or_err;
|
|
|
|
if (!owned_script_object_sp->IsValid())
|
|
return llvm::createStringError(llvm::inconvertibleErrorCode(),
|
|
"Created script object is invalid.");
|
|
|
|
lldb::tid_t tid = scripted_thread_interface->GetThreadID();
|
|
|
|
return std::make_shared<ScriptedThread>(process, scripted_thread_interface,
|
|
tid, owned_script_object_sp);
|
|
}
|
|
|
|
ScriptedThread::ScriptedThread(ScriptedProcess &process,
|
|
ScriptedThreadInterfaceSP interface_sp,
|
|
lldb::tid_t tid,
|
|
StructuredData::GenericSP script_object_sp)
|
|
: Thread(process, tid), m_scripted_process(process),
|
|
m_scripted_thread_interface_sp(interface_sp),
|
|
m_script_object_sp(script_object_sp) {}
|
|
|
|
ScriptedThread::~ScriptedThread() { DestroyThread(); }
|
|
|
|
const char *ScriptedThread::GetName() {
|
|
CheckInterpreterAndScriptObject();
|
|
std::optional<std::string> thread_name = GetInterface()->GetName();
|
|
if (!thread_name)
|
|
return nullptr;
|
|
return ConstString(*thread_name).AsCString(nullptr);
|
|
}
|
|
|
|
const char *ScriptedThread::GetQueueName() {
|
|
CheckInterpreterAndScriptObject();
|
|
std::optional<std::string> queue_name = GetInterface()->GetQueue();
|
|
if (!queue_name)
|
|
return nullptr;
|
|
return ConstString(*queue_name).AsCString(nullptr);
|
|
}
|
|
|
|
void ScriptedThread::WillResume(StateType resume_state) {}
|
|
|
|
void ScriptedThread::ClearStackFrames() { Thread::ClearStackFrames(); }
|
|
|
|
RegisterContextSP ScriptedThread::GetRegisterContext() {
|
|
if (!m_reg_context_sp)
|
|
m_reg_context_sp = CreateRegisterContextForFrame(nullptr);
|
|
return m_reg_context_sp;
|
|
}
|
|
|
|
RegisterContextSP
|
|
ScriptedThread::CreateRegisterContextForFrame(StackFrame *frame) {
|
|
const uint32_t concrete_frame_idx =
|
|
frame ? frame->GetConcreteFrameIndex() : 0;
|
|
|
|
if (concrete_frame_idx)
|
|
return GetUnwinder().CreateRegisterContextForFrame(frame);
|
|
|
|
lldb::RegisterContextSP reg_ctx_sp;
|
|
Status error;
|
|
|
|
std::optional<std::string> reg_data = GetInterface()->GetRegisterContext();
|
|
if (!reg_data)
|
|
return ScriptedInterface::ErrorWithMessage<lldb::RegisterContextSP>(
|
|
LLVM_PRETTY_FUNCTION, "Failed to get scripted thread registers data.",
|
|
error, LLDBLog::Thread);
|
|
|
|
DataBufferSP data_sp(
|
|
std::make_shared<DataBufferHeap>(reg_data->c_str(), reg_data->size()));
|
|
|
|
if (!data_sp->GetByteSize())
|
|
return ScriptedInterface::ErrorWithMessage<lldb::RegisterContextSP>(
|
|
LLVM_PRETTY_FUNCTION, "Failed to copy raw registers data.", error,
|
|
LLDBLog::Thread);
|
|
|
|
std::shared_ptr<RegisterContextMemory> reg_ctx_memory =
|
|
std::make_shared<RegisterContextMemory>(
|
|
*this, 0, *GetDynamicRegisterInfo(), LLDB_INVALID_ADDRESS);
|
|
if (!reg_ctx_memory)
|
|
return ScriptedInterface::ErrorWithMessage<lldb::RegisterContextSP>(
|
|
LLVM_PRETTY_FUNCTION, "Failed to create a register context.", error,
|
|
LLDBLog::Thread);
|
|
|
|
reg_ctx_memory->SetAllRegisterData(data_sp);
|
|
m_reg_context_sp = reg_ctx_memory;
|
|
|
|
return m_reg_context_sp;
|
|
}
|
|
|
|
bool ScriptedThread::LoadArtificialStackFrames() {
|
|
StructuredData::ArraySP arr_sp = GetInterface()->GetStackFrames();
|
|
|
|
Status error;
|
|
if (!arr_sp)
|
|
return ScriptedInterface::ErrorWithMessage<bool>(
|
|
LLVM_PRETTY_FUNCTION, "Failed to get scripted thread stackframes.",
|
|
error, LLDBLog::Thread);
|
|
|
|
size_t arr_size = arr_sp->GetSize();
|
|
if (!arr_size)
|
|
return ScriptedInterface::ErrorWithMessage<bool>(
|
|
LLVM_PRETTY_FUNCTION, "StackFrame array is empty.", error,
|
|
LLDBLog::Thread);
|
|
|
|
if (arr_size > std::numeric_limits<uint32_t>::max())
|
|
return ScriptedInterface::ErrorWithMessage<bool>(
|
|
LLVM_PRETTY_FUNCTION,
|
|
llvm::Twine(
|
|
"StackFrame array size (" + llvm::Twine(arr_size) +
|
|
") is greater than maximum authorized for a StackFrameList.")
|
|
.str(),
|
|
error, LLDBLog::Thread);
|
|
|
|
auto create_frame_from_dict =
|
|
[this, arr_sp](size_t idx,
|
|
uint32_t frame_list_idx) -> llvm::Expected<StackFrameSP> {
|
|
Status error;
|
|
std::optional<StructuredData::Dictionary *> maybe_dict =
|
|
arr_sp->GetItemAtIndexAsDictionary(idx);
|
|
if (!maybe_dict) {
|
|
ScriptedInterface::ErrorWithMessage<bool>(
|
|
LLVM_PRETTY_FUNCTION,
|
|
llvm::Twine(
|
|
"Couldn't get artificial stackframe dictionary at index (" +
|
|
llvm::Twine(idx) + llvm::Twine(") from stackframe array."))
|
|
.str(),
|
|
error, LLDBLog::Thread);
|
|
return error.ToError();
|
|
}
|
|
StructuredData::Dictionary *dict = *maybe_dict;
|
|
|
|
lldb::addr_t pc;
|
|
if (!dict->GetValueForKeyAsInteger("pc", pc)) {
|
|
ScriptedInterface::ErrorWithMessage<bool>(
|
|
LLVM_PRETTY_FUNCTION,
|
|
"Couldn't find value for key 'pc' in stackframe dictionary.", error,
|
|
LLDBLog::Thread);
|
|
return error.ToError();
|
|
}
|
|
|
|
Address symbol_addr;
|
|
symbol_addr.SetLoadAddress(pc, &this->GetProcess()->GetTarget());
|
|
|
|
lldb::addr_t cfa = LLDB_INVALID_ADDRESS;
|
|
bool cfa_is_valid = false;
|
|
const bool artificial = false;
|
|
const bool behaves_like_zeroth_frame = (frame_list_idx == 0);
|
|
SymbolContext sc;
|
|
symbol_addr.CalculateSymbolContext(&sc);
|
|
|
|
return std::make_shared<StackFrame>(shared_from_this(), frame_list_idx, idx,
|
|
cfa, cfa_is_valid, pc,
|
|
StackFrame::Kind::Synthetic, artificial,
|
|
behaves_like_zeroth_frame, &sc);
|
|
};
|
|
|
|
auto create_frame_from_script_object =
|
|
[this, arr_sp](size_t idx) -> llvm::Expected<StackFrameSP> {
|
|
Status error;
|
|
StructuredData::ObjectSP object_sp = arr_sp->GetItemAtIndex(idx);
|
|
if (!object_sp || !object_sp->GetAsGeneric()) {
|
|
ScriptedInterface::ErrorWithMessage<bool>(
|
|
LLVM_PRETTY_FUNCTION,
|
|
llvm::Twine("Couldn't get artificial stackframe object at index (" +
|
|
llvm::Twine(idx) +
|
|
llvm::Twine(") from stackframe array."))
|
|
.str(),
|
|
error, LLDBLog::Thread);
|
|
return error.ToError();
|
|
}
|
|
|
|
auto frame_or_error = ScriptedFrame::Create(
|
|
shared_from_this(), GetInterface(), nullptr, object_sp->GetAsGeneric());
|
|
|
|
if (!frame_or_error) {
|
|
ScriptedInterface::ErrorWithMessage<bool>(
|
|
LLVM_PRETTY_FUNCTION, toString(frame_or_error.takeError()), error);
|
|
return error.ToError();
|
|
}
|
|
|
|
StackFrameSP frame_sp = frame_or_error.get();
|
|
lldbassert(frame_sp && "Couldn't initialize scripted frame.");
|
|
|
|
return frame_sp;
|
|
};
|
|
|
|
StackFrameListSP frames = GetStackFrameList();
|
|
uint32_t frame_list_idx = 0;
|
|
|
|
for (size_t idx = 0; idx < arr_size; idx++) {
|
|
StackFrameSP synth_frame_sp = nullptr;
|
|
|
|
auto frame_from_dict_or_err = create_frame_from_dict(idx, frame_list_idx);
|
|
if (!frame_from_dict_or_err) {
|
|
auto frame_from_script_obj_or_err = create_frame_from_script_object(idx);
|
|
|
|
if (!frame_from_script_obj_or_err) {
|
|
return ScriptedInterface::ErrorWithMessage<bool>(
|
|
LLVM_PRETTY_FUNCTION,
|
|
llvm::Twine(
|
|
"Couldn't add artificial frame (" + llvm::Twine(idx) +
|
|
llvm::Twine(") to ScriptedThread StackFrameList: ") +
|
|
llvm::toString(frame_from_script_obj_or_err.takeError()))
|
|
.str(),
|
|
error, LLDBLog::Thread);
|
|
} else {
|
|
llvm::consumeError(frame_from_dict_or_err.takeError());
|
|
synth_frame_sp = *frame_from_script_obj_or_err;
|
|
}
|
|
} else {
|
|
synth_frame_sp = *frame_from_dict_or_err;
|
|
}
|
|
|
|
if (!frames->SetFrameAtIndex(frame_list_idx, synth_frame_sp))
|
|
return ScriptedInterface::ErrorWithMessage<bool>(
|
|
LLVM_PRETTY_FUNCTION,
|
|
llvm::Twine("Couldn't add frame (" + llvm::Twine(idx) +
|
|
llvm::Twine(") to ScriptedThread StackFrameList."))
|
|
.str(),
|
|
error, LLDBLog::Thread);
|
|
frame_list_idx++;
|
|
|
|
// Synthesize inline frames, mirroring StackFrameList::FetchFramesUpTo().
|
|
frame_list_idx += frames->SynthesizeInlineFrames(
|
|
synth_frame_sp, /*cfa=*/LLDB_INVALID_ADDRESS);
|
|
}
|
|
|
|
// Mark the stack as fully unwound so the regular unwinder doesn't try to
|
|
// extend it beyond the artificial frames (e.g. by reading lr/fp from the
|
|
// register context).
|
|
frames->SetAllFramesFetched();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ScriptedThread::CalculateStopInfo() {
|
|
StructuredData::DictionarySP dict_sp = GetInterface()->GetStopReason();
|
|
|
|
Status error;
|
|
if (!dict_sp)
|
|
return ScriptedInterface::ErrorWithMessage<bool>(
|
|
LLVM_PRETTY_FUNCTION, "Failed to get scripted thread stop info.", error,
|
|
LLDBLog::Thread);
|
|
|
|
// If we're at a BreakpointSite, mark that we stopped there and
|
|
// need to hit the breakpoint when we resume. This will be cleared
|
|
// if we CreateStopReasonWithBreakpointSiteID.
|
|
if (RegisterContextSP reg_ctx_sp = GetRegisterContext()) {
|
|
addr_t pc = reg_ctx_sp->GetPC();
|
|
ProcessSP proc = GetProcess();
|
|
if (BreakpointSiteSP bp_site_sp =
|
|
proc->GetBreakpointSiteList().FindByAddress(pc))
|
|
if (proc->IsBreakpointSitePhysicallyEnabled(*bp_site_sp))
|
|
SetThreadStoppedAtUnexecutedBP(pc);
|
|
}
|
|
|
|
lldb::StopInfoSP stop_info_sp;
|
|
lldb::StopReason stop_reason_type;
|
|
|
|
if (!dict_sp->GetValueForKeyAsInteger("type", stop_reason_type))
|
|
return ScriptedInterface::ErrorWithMessage<bool>(
|
|
LLVM_PRETTY_FUNCTION,
|
|
"Couldn't find value for key 'type' in stop reason dictionary.", error,
|
|
LLDBLog::Thread);
|
|
|
|
StructuredData::Dictionary *data_dict;
|
|
if (!dict_sp->GetValueForKeyAsDictionary("data", data_dict))
|
|
return ScriptedInterface::ErrorWithMessage<bool>(
|
|
LLVM_PRETTY_FUNCTION,
|
|
"Couldn't find value for key 'data' in stop reason dictionary.", error,
|
|
LLDBLog::Thread);
|
|
|
|
switch (stop_reason_type) {
|
|
case lldb::eStopReasonNone:
|
|
return true;
|
|
case lldb::eStopReasonBreakpoint: {
|
|
lldb::break_id_t break_id;
|
|
data_dict->GetValueForKeyAsInteger("break_id", break_id,
|
|
LLDB_INVALID_BREAK_ID);
|
|
stop_info_sp =
|
|
StopInfo::CreateStopReasonWithBreakpointSiteID(*this, break_id);
|
|
} break;
|
|
case lldb::eStopReasonSignal: {
|
|
uint32_t signal;
|
|
llvm::StringRef description;
|
|
if (!data_dict->GetValueForKeyAsInteger("signal", signal)) {
|
|
signal = LLDB_INVALID_SIGNAL_NUMBER;
|
|
return false;
|
|
}
|
|
data_dict->GetValueForKeyAsString("desc", description);
|
|
stop_info_sp =
|
|
StopInfo::CreateStopReasonWithSignal(*this, signal, description.data());
|
|
} break;
|
|
case lldb::eStopReasonTrace: {
|
|
stop_info_sp = StopInfo::CreateStopReasonToTrace(*this);
|
|
} break;
|
|
case lldb::eStopReasonException: {
|
|
#if defined(__APPLE__)
|
|
StructuredData::Dictionary *mach_exception;
|
|
if (data_dict->GetValueForKeyAsDictionary("mach_exception",
|
|
mach_exception)) {
|
|
llvm::StringRef value;
|
|
mach_exception->GetValueForKeyAsString("type", value);
|
|
auto exc_type =
|
|
StopInfoMachException::MachException::ExceptionCode(value.data());
|
|
|
|
if (!exc_type)
|
|
return false;
|
|
|
|
uint32_t exc_data_size = 0;
|
|
llvm::SmallVector<uint64_t, 3> raw_codes;
|
|
|
|
StructuredData::Array *exc_rawcodes;
|
|
mach_exception->GetValueForKeyAsArray("rawCodes", exc_rawcodes);
|
|
if (exc_rawcodes) {
|
|
auto fetch_data = [&raw_codes](StructuredData::Object *obj) {
|
|
if (!obj)
|
|
return false;
|
|
raw_codes.push_back(obj->GetUnsignedIntegerValue());
|
|
return true;
|
|
};
|
|
|
|
exc_rawcodes->ForEach(fetch_data);
|
|
exc_data_size = raw_codes.size();
|
|
}
|
|
|
|
stop_info_sp = StopInfoMachException::CreateStopReasonWithMachException(
|
|
*this, *exc_type, exc_data_size,
|
|
exc_data_size >= 1 ? raw_codes[0] : 0,
|
|
exc_data_size >= 2 ? raw_codes[1] : 0,
|
|
exc_data_size >= 3 ? raw_codes[2] : 0);
|
|
|
|
break;
|
|
}
|
|
#endif
|
|
stop_info_sp =
|
|
StopInfo::CreateStopReasonWithException(*this, "EXC_BAD_ACCESS");
|
|
} break;
|
|
default:
|
|
return ScriptedInterface::ErrorWithMessage<bool>(
|
|
LLVM_PRETTY_FUNCTION,
|
|
llvm::Twine("Unsupported stop reason type (" +
|
|
llvm::Twine(stop_reason_type) + llvm::Twine(")."))
|
|
.str(),
|
|
error, LLDBLog::Thread);
|
|
}
|
|
|
|
if (!stop_info_sp)
|
|
return false;
|
|
|
|
SetStopInfo(stop_info_sp);
|
|
return true;
|
|
}
|
|
|
|
void ScriptedThread::RefreshStateAfterStop() {
|
|
GetRegisterContext()->InvalidateIfNeeded(/*force=*/false);
|
|
LoadArtificialStackFrames();
|
|
}
|
|
|
|
lldb::ScriptedThreadInterfaceSP ScriptedThread::GetInterface() const {
|
|
return m_scripted_thread_interface_sp;
|
|
}
|
|
|
|
std::shared_ptr<DynamicRegisterInfo> ScriptedThread::GetDynamicRegisterInfo() {
|
|
CheckInterpreterAndScriptObject();
|
|
|
|
if (!m_register_info_sp) {
|
|
StructuredData::DictionarySP reg_info = GetInterface()->GetRegisterInfo();
|
|
|
|
Status error;
|
|
if (!reg_info)
|
|
return ScriptedInterface::ErrorWithMessage<
|
|
std::shared_ptr<DynamicRegisterInfo>>(
|
|
LLVM_PRETTY_FUNCTION, "Failed to get scripted thread registers info.",
|
|
error, LLDBLog::Thread);
|
|
|
|
m_register_info_sp = DynamicRegisterInfo::Create(
|
|
*reg_info, m_scripted_process.GetTarget().GetArchitecture());
|
|
}
|
|
|
|
return m_register_info_sp;
|
|
}
|
|
|
|
StructuredData::ObjectSP ScriptedThread::FetchThreadExtendedInfo() {
|
|
CheckInterpreterAndScriptObject();
|
|
|
|
Status error;
|
|
StructuredData::ArraySP extended_info_sp = GetInterface()->GetExtendedInfo();
|
|
|
|
if (!extended_info_sp || !extended_info_sp->GetSize())
|
|
return ScriptedInterface::ErrorWithMessage<StructuredData::ObjectSP>(
|
|
LLVM_PRETTY_FUNCTION, "No extended information found", error);
|
|
|
|
return extended_info_sp;
|
|
}
|