mirror of
https://github.com/intel/llvm.git
synced 2026-02-02 02:00:03 +08:00
[PAC][lldb][Dwarf] Support __ptrauth-qualified types in user expressions (#84387)
Depends on #84384 and #90329 This adds support for `DW_TAG_LLVM_ptrauth_type` entries corresponding to explicitly signed types (e.g. free function pointers) in lldb user expressions. Applies PR https://github.com/apple/llvm-project/pull/8239 from Apple's downstream and also adds tests and related code. --------- Co-authored-by: Jonas Devlieghere <jonas@devlieghere.com>
This commit is contained in:
@@ -262,6 +262,12 @@ public:
|
||||
size_t GetPointerByteSize() const;
|
||||
/// \}
|
||||
|
||||
unsigned GetPtrAuthKey() const;
|
||||
|
||||
unsigned GetPtrAuthDiscriminator() const;
|
||||
|
||||
bool GetPtrAuthAddressDiversity() const;
|
||||
|
||||
/// Accessors.
|
||||
/// \{
|
||||
|
||||
@@ -369,6 +375,12 @@ public:
|
||||
|
||||
/// Create related types using the current type's AST
|
||||
CompilerType GetBasicTypeFromAST(lldb::BasicType basic_type) const;
|
||||
|
||||
/// Return a new CompilerType adds a ptrauth modifier from the given 32-bit
|
||||
/// opaque payload to this type if this type is valid and the type system
|
||||
/// supports ptrauth modifiers, else return an invalid type. Note that this
|
||||
/// does not check if this type is a pointer.
|
||||
CompilerType AddPtrAuthModifier(uint32_t payload) const;
|
||||
/// \}
|
||||
|
||||
/// Exploring the type.
|
||||
|
||||
@@ -401,7 +401,9 @@ public:
|
||||
/// This type is the type whose UID is m_encoding_uid as an atomic type.
|
||||
eEncodingIsAtomicUID,
|
||||
/// This type is the synthetic type whose UID is m_encoding_uid.
|
||||
eEncodingIsSyntheticUID
|
||||
eEncodingIsSyntheticUID,
|
||||
/// This type is a signed pointer.
|
||||
eEncodingIsLLVMPtrAuthUID
|
||||
};
|
||||
|
||||
enum class ResolveState : unsigned char {
|
||||
|
||||
@@ -221,6 +221,14 @@ public:
|
||||
|
||||
virtual uint32_t GetPointerByteSize() = 0;
|
||||
|
||||
virtual unsigned GetPtrAuthKey(lldb::opaque_compiler_type_t type) = 0;
|
||||
|
||||
virtual unsigned
|
||||
GetPtrAuthDiscriminator(lldb::opaque_compiler_type_t type) = 0;
|
||||
|
||||
virtual bool
|
||||
GetPtrAuthAddressDiversity(lldb::opaque_compiler_type_t type) = 0;
|
||||
|
||||
// Accessors
|
||||
|
||||
virtual ConstString GetTypeName(lldb::opaque_compiler_type_t type,
|
||||
@@ -285,6 +293,9 @@ public:
|
||||
|
||||
virtual CompilerType AddRestrictModifier(lldb::opaque_compiler_type_t type);
|
||||
|
||||
virtual CompilerType AddPtrAuthModifier(lldb::opaque_compiler_type_t type,
|
||||
uint32_t payload);
|
||||
|
||||
/// \param opaque_payload The m_payload field of Type, which may
|
||||
/// carry TypeSystem-specific extra information.
|
||||
virtual CompilerType CreateTypedef(lldb::opaque_compiler_type_t type,
|
||||
|
||||
@@ -570,6 +570,39 @@ ExtractDataMemberLocation(DWARFDIE const &die, DWARFFormValue const &form_value,
|
||||
return memberOffset.ResolveValue(nullptr).UInt();
|
||||
}
|
||||
|
||||
static TypePayloadClang GetPtrAuthMofidierPayload(const DWARFDIE &die) {
|
||||
auto getAttr = [&](llvm::dwarf::Attribute Attr, unsigned defaultValue = 0) {
|
||||
return die.GetAttributeValueAsUnsigned(Attr, defaultValue);
|
||||
};
|
||||
const unsigned key = getAttr(DW_AT_LLVM_ptrauth_key);
|
||||
const bool addr_disc = getAttr(DW_AT_LLVM_ptrauth_address_discriminated);
|
||||
const unsigned extra = getAttr(DW_AT_LLVM_ptrauth_extra_discriminator);
|
||||
const bool isapointer = getAttr(DW_AT_LLVM_ptrauth_isa_pointer);
|
||||
const bool authenticates_null_values =
|
||||
getAttr(DW_AT_LLVM_ptrauth_authenticates_null_values);
|
||||
const unsigned authentication_mode_int = getAttr(
|
||||
DW_AT_LLVM_ptrauth_authentication_mode,
|
||||
static_cast<unsigned>(clang::PointerAuthenticationMode::SignAndAuth));
|
||||
clang::PointerAuthenticationMode authentication_mode =
|
||||
clang::PointerAuthenticationMode::SignAndAuth;
|
||||
if (authentication_mode_int >=
|
||||
static_cast<unsigned>(clang::PointerAuthenticationMode::None) &&
|
||||
authentication_mode_int <=
|
||||
static_cast<unsigned>(
|
||||
clang::PointerAuthenticationMode::SignAndAuth)) {
|
||||
authentication_mode =
|
||||
static_cast<clang::PointerAuthenticationMode>(authentication_mode_int);
|
||||
} else {
|
||||
die.GetDWARF()->GetObjectFile()->GetModule()->ReportError(
|
||||
"[{0:x16}]: invalid pointer authentication mode method {1:x4}",
|
||||
die.GetOffset(), authentication_mode_int);
|
||||
}
|
||||
auto ptr_auth = clang::PointerAuthQualifier::Create(
|
||||
key, addr_disc, extra, authentication_mode, isapointer,
|
||||
authenticates_null_values);
|
||||
return TypePayloadClang(ptr_auth.getAsOpaqueValue());
|
||||
}
|
||||
|
||||
lldb::TypeSP
|
||||
DWARFASTParserClang::ParseTypeModifier(const SymbolContext &sc,
|
||||
const DWARFDIE &die,
|
||||
@@ -580,6 +613,7 @@ DWARFASTParserClang::ParseTypeModifier(const SymbolContext &sc,
|
||||
LanguageType cu_language = SymbolFileDWARF::GetLanguage(*die.GetCU());
|
||||
Type::ResolveState resolve_state = Type::ResolveState::Unresolved;
|
||||
Type::EncodingDataType encoding_data_type = Type::eEncodingIsUID;
|
||||
TypePayloadClang payload(GetOwningClangModule(die));
|
||||
TypeSP type_sp;
|
||||
CompilerType clang_type;
|
||||
|
||||
@@ -677,6 +711,10 @@ DWARFASTParserClang::ParseTypeModifier(const SymbolContext &sc,
|
||||
case DW_TAG_volatile_type:
|
||||
encoding_data_type = Type::eEncodingIsVolatileUID;
|
||||
break;
|
||||
case DW_TAG_LLVM_ptrauth_type:
|
||||
encoding_data_type = Type::eEncodingIsLLVMPtrAuthUID;
|
||||
payload = GetPtrAuthMofidierPayload(die);
|
||||
break;
|
||||
case DW_TAG_atomic_type:
|
||||
encoding_data_type = Type::eEncodingIsAtomicUID;
|
||||
break;
|
||||
@@ -786,8 +824,7 @@ DWARFASTParserClang::ParseTypeModifier(const SymbolContext &sc,
|
||||
|
||||
type_sp = dwarf->MakeType(die.GetID(), attrs.name, attrs.byte_size, nullptr,
|
||||
attrs.type.Reference().GetID(), encoding_data_type,
|
||||
&attrs.decl, clang_type, resolve_state,
|
||||
TypePayloadClang(GetOwningClangModule(die)));
|
||||
&attrs.decl, clang_type, resolve_state, payload);
|
||||
|
||||
dwarf->GetDIEToType()[die.GetDIE()] = type_sp.get();
|
||||
return type_sp;
|
||||
|
||||
@@ -2923,6 +2923,35 @@ bool TypeSystemClang::IsCStringType(lldb::opaque_compiler_type_t type,
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned TypeSystemClang::GetPtrAuthKey(lldb::opaque_compiler_type_t type) {
|
||||
if (type) {
|
||||
clang::QualType qual_type(GetCanonicalQualType(type));
|
||||
if (auto pointer_auth = qual_type.getPointerAuth())
|
||||
return pointer_auth.getKey();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
unsigned
|
||||
TypeSystemClang::GetPtrAuthDiscriminator(lldb::opaque_compiler_type_t type) {
|
||||
if (type) {
|
||||
clang::QualType qual_type(GetCanonicalQualType(type));
|
||||
if (auto pointer_auth = qual_type.getPointerAuth())
|
||||
return pointer_auth.getExtraDiscriminator();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool TypeSystemClang::GetPtrAuthAddressDiversity(
|
||||
lldb::opaque_compiler_type_t type) {
|
||||
if (type) {
|
||||
clang::QualType qual_type(GetCanonicalQualType(type));
|
||||
if (auto pointer_auth = qual_type.getPointerAuth())
|
||||
return pointer_auth.isAddressDiscriminated();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TypeSystemClang::IsFunctionType(lldb::opaque_compiler_type_t type) {
|
||||
auto isFunctionType = [&](clang::QualType qual_type) {
|
||||
return qual_type->isFunctionType();
|
||||
@@ -4511,6 +4540,19 @@ TypeSystemClang::AddConstModifier(lldb::opaque_compiler_type_t type) {
|
||||
return CompilerType();
|
||||
}
|
||||
|
||||
CompilerType
|
||||
TypeSystemClang::AddPtrAuthModifier(lldb::opaque_compiler_type_t type,
|
||||
uint32_t payload) {
|
||||
if (type) {
|
||||
clang::ASTContext &clang_ast = getASTContext();
|
||||
auto pauth = PointerAuthQualifier::fromOpaqueValue(payload);
|
||||
clang::QualType result =
|
||||
clang_ast.getPointerAuthType(GetQualType(type), pauth);
|
||||
return GetType(result);
|
||||
}
|
||||
return CompilerType();
|
||||
}
|
||||
|
||||
CompilerType
|
||||
TypeSystemClang::AddVolatileModifier(lldb::opaque_compiler_type_t type) {
|
||||
if (type) {
|
||||
|
||||
@@ -67,11 +67,13 @@ public:
|
||||
|
||||
/// The implementation of lldb::Type's m_payload field for TypeSystemClang.
|
||||
class TypePayloadClang {
|
||||
/// The Layout is as follows:
|
||||
/// The payload is used for typedefs and ptrauth types.
|
||||
/// For typedefs, the Layout is as follows:
|
||||
/// \verbatim
|
||||
/// bit 0..30 ... Owning Module ID.
|
||||
/// bit 31 ...... IsCompleteObjCClass.
|
||||
/// \endverbatim
|
||||
/// For ptrauth types, we store the PointerAuthQualifier as an opaque value.
|
||||
Type::Payload m_payload = 0;
|
||||
|
||||
public:
|
||||
@@ -653,6 +655,10 @@ public:
|
||||
bool IsFloatingPointType(lldb::opaque_compiler_type_t type, uint32_t &count,
|
||||
bool &is_complex) override;
|
||||
|
||||
unsigned GetPtrAuthKey(lldb::opaque_compiler_type_t type) override;
|
||||
unsigned GetPtrAuthDiscriminator(lldb::opaque_compiler_type_t type) override;
|
||||
bool GetPtrAuthAddressDiversity(lldb::opaque_compiler_type_t type) override;
|
||||
|
||||
bool IsFunctionType(lldb::opaque_compiler_type_t type) override;
|
||||
|
||||
uint32_t IsHomogeneousAggregate(lldb::opaque_compiler_type_t type,
|
||||
@@ -793,6 +799,9 @@ public:
|
||||
|
||||
CompilerType AddConstModifier(lldb::opaque_compiler_type_t type) override;
|
||||
|
||||
CompilerType AddPtrAuthModifier(lldb::opaque_compiler_type_t type,
|
||||
uint32_t payload) override;
|
||||
|
||||
CompilerType AddVolatileModifier(lldb::opaque_compiler_type_t type) override;
|
||||
|
||||
CompilerType AddRestrictModifier(lldb::opaque_compiler_type_t type) override;
|
||||
|
||||
@@ -108,6 +108,27 @@ bool CompilerType::IsConst() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned CompilerType::GetPtrAuthKey() const {
|
||||
if (IsValid())
|
||||
if (auto type_system_sp = GetTypeSystem())
|
||||
return type_system_sp->GetPtrAuthKey(m_type);
|
||||
return 0;
|
||||
}
|
||||
|
||||
unsigned CompilerType::GetPtrAuthDiscriminator() const {
|
||||
if (IsValid())
|
||||
if (auto type_system_sp = GetTypeSystem())
|
||||
return type_system_sp->GetPtrAuthDiscriminator(m_type);
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool CompilerType::GetPtrAuthAddressDiversity() const {
|
||||
if (IsValid())
|
||||
if (auto type_system_sp = GetTypeSystem())
|
||||
return type_system_sp->GetPtrAuthAddressDiversity(m_type);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CompilerType::IsFunctionType() const {
|
||||
if (IsValid())
|
||||
if (auto type_system_sp = GetTypeSystem())
|
||||
@@ -664,6 +685,13 @@ CompilerType CompilerType::GetPointerType() const {
|
||||
return CompilerType();
|
||||
}
|
||||
|
||||
CompilerType CompilerType::AddPtrAuthModifier(uint32_t payload) const {
|
||||
if (IsValid())
|
||||
if (auto type_system_sp = GetTypeSystem())
|
||||
return type_system_sp->AddPtrAuthModifier(m_type, payload);
|
||||
return CompilerType();
|
||||
}
|
||||
|
||||
CompilerType CompilerType::GetLValueReferenceType() const {
|
||||
if (IsValid())
|
||||
if (auto type_system_sp = GetTypeSystem())
|
||||
|
||||
@@ -355,6 +355,9 @@ void Type::GetDescription(Stream *s, lldb::DescriptionLevel level,
|
||||
case eEncodingIsSyntheticUID:
|
||||
s->PutCString(" (synthetic type)");
|
||||
break;
|
||||
case eEncodingIsLLVMPtrAuthUID:
|
||||
s->PutCString(" (ptrauth type)");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -416,6 +419,8 @@ void Type::Dump(Stream *s, bool show_context, lldb::DescriptionLevel level) {
|
||||
case eEncodingIsSyntheticUID:
|
||||
s->PutCString(" (synthetic type)");
|
||||
break;
|
||||
case eEncodingIsLLVMPtrAuthUID:
|
||||
s->PutCString(" (ptrauth type)");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -477,7 +482,8 @@ std::optional<uint64_t> Type::GetByteSize(ExecutionContextScope *exe_scope) {
|
||||
// If we are a pointer or reference, then this is just a pointer size;
|
||||
case eEncodingIsPointerUID:
|
||||
case eEncodingIsLValueReferenceUID:
|
||||
case eEncodingIsRValueReferenceUID: {
|
||||
case eEncodingIsRValueReferenceUID:
|
||||
case eEncodingIsLLVMPtrAuthUID: {
|
||||
if (ArchSpec arch = m_symbol_file->GetObjectFile()->GetArchitecture()) {
|
||||
m_byte_size = arch.GetAddressByteSize();
|
||||
m_byte_size_has_value = true;
|
||||
@@ -621,6 +627,12 @@ bool Type::ResolveCompilerType(ResolveState compiler_type_resolve_state) {
|
||||
encoding_type->GetForwardCompilerType().GetRValueReferenceType();
|
||||
break;
|
||||
|
||||
case eEncodingIsLLVMPtrAuthUID:
|
||||
m_compiler_type =
|
||||
encoding_type->GetForwardCompilerType().AddPtrAuthModifier(
|
||||
m_payload);
|
||||
break;
|
||||
|
||||
default:
|
||||
llvm_unreachable("Unhandled encoding_data_type.");
|
||||
}
|
||||
@@ -676,6 +688,10 @@ bool Type::ResolveCompilerType(ResolveState compiler_type_resolve_state) {
|
||||
m_compiler_type = void_compiler_type.GetRValueReferenceType();
|
||||
break;
|
||||
|
||||
case eEncodingIsLLVMPtrAuthUID:
|
||||
llvm_unreachable("Cannot handle eEncodingIsLLVMPtrAuthUID without "
|
||||
"valid encoding_type");
|
||||
|
||||
default:
|
||||
llvm_unreachable("Unhandled encoding_data_type.");
|
||||
}
|
||||
|
||||
@@ -93,6 +93,11 @@ CompilerType TypeSystem::AddConstModifier(lldb::opaque_compiler_type_t type) {
|
||||
return CompilerType();
|
||||
}
|
||||
|
||||
CompilerType TypeSystem::AddPtrAuthModifier(lldb::opaque_compiler_type_t type,
|
||||
uint32_t payload) {
|
||||
return CompilerType();
|
||||
}
|
||||
|
||||
CompilerType
|
||||
TypeSystem::AddVolatileModifier(lldb::opaque_compiler_type_t type) {
|
||||
return CompilerType();
|
||||
|
||||
@@ -287,6 +287,141 @@ DWARF:
|
||||
ASSERT_EQ(found_function_types, expected_function_types);
|
||||
}
|
||||
|
||||
TEST_F(DWARFASTParserClangTests, TestPtrAuthParsing) {
|
||||
// Tests parsing values with type DW_TAG_LLVM_ptrauth_type corresponding to
|
||||
// explicitly signed raw function pointers
|
||||
|
||||
// This is Dwarf for the following C code:
|
||||
// ```
|
||||
// void (*__ptrauth(0, 0, 42) a)();
|
||||
// ```
|
||||
|
||||
const char *yamldata = R"(
|
||||
--- !ELF
|
||||
FileHeader:
|
||||
Class: ELFCLASS64
|
||||
Data: ELFDATA2LSB
|
||||
Type: ET_EXEC
|
||||
Machine: EM_AARCH64
|
||||
DWARF:
|
||||
debug_str:
|
||||
- a
|
||||
debug_abbrev:
|
||||
- ID: 0
|
||||
Table:
|
||||
- Code: 0x01
|
||||
Tag: DW_TAG_compile_unit
|
||||
Children: DW_CHILDREN_yes
|
||||
Attributes:
|
||||
- Attribute: DW_AT_language
|
||||
Form: DW_FORM_data2
|
||||
- Code: 0x02
|
||||
Tag: DW_TAG_variable
|
||||
Children: DW_CHILDREN_no
|
||||
Attributes:
|
||||
- Attribute: DW_AT_name
|
||||
Form: DW_FORM_strp
|
||||
- Attribute: DW_AT_type
|
||||
Form: DW_FORM_ref4
|
||||
- Attribute: DW_AT_external
|
||||
Form: DW_FORM_flag_present
|
||||
- Code: 0x03
|
||||
Tag: DW_TAG_LLVM_ptrauth_type
|
||||
Children: DW_CHILDREN_no
|
||||
Attributes:
|
||||
- Attribute: DW_AT_type
|
||||
Form: DW_FORM_ref4
|
||||
- Attribute: DW_AT_LLVM_ptrauth_key
|
||||
Form: DW_FORM_data1
|
||||
- Attribute: DW_AT_LLVM_ptrauth_extra_discriminator
|
||||
Form: DW_FORM_data2
|
||||
- Code: 0x04
|
||||
Tag: DW_TAG_pointer_type
|
||||
Children: DW_CHILDREN_no
|
||||
Attributes:
|
||||
- Attribute: DW_AT_type
|
||||
Form: DW_FORM_ref4
|
||||
- Code: 0x05
|
||||
Tag: DW_TAG_subroutine_type
|
||||
Children: DW_CHILDREN_yes
|
||||
- Code: 0x06
|
||||
Tag: DW_TAG_unspecified_parameters
|
||||
Children: DW_CHILDREN_no
|
||||
|
||||
debug_info:
|
||||
- Version: 5
|
||||
UnitType: DW_UT_compile
|
||||
AddrSize: 8
|
||||
Entries:
|
||||
# 0x0c: DW_TAG_compile_unit
|
||||
# DW_AT_language [DW_FORM_data2] (DW_LANG_C99)
|
||||
- AbbrCode: 0x01
|
||||
Values:
|
||||
- Value: 0x0c
|
||||
|
||||
# 0x0f: DW_TAG_variable
|
||||
# DW_AT_name [DW_FORM_strp] (\"a\")
|
||||
# DW_AT_type [DW_FORM_ref4] (0x00000018 \"void (*__ptrauth(0, 0, 0x02a)\")
|
||||
# DW_AT_external [DW_FORM_flag_present] (true)
|
||||
- AbbrCode: 0x02
|
||||
Values:
|
||||
- Value: 0x00
|
||||
- Value: 0x18
|
||||
|
||||
# 0x18: DW_TAG_LLVM_ptrauth_type
|
||||
# DW_AT_type [DW_FORM_ref4] (0x00000020 \"void (*)(...)\")
|
||||
# DW_AT_LLVM_ptrauth_key [DW_FORM_data1] (0x00)
|
||||
# DW_AT_LLVM_ptrauth_extra_discriminator [DW_FORM_data2] (0x002a)
|
||||
- AbbrCode: 0x03
|
||||
Values:
|
||||
- Value: 0x20
|
||||
- Value: 0x00
|
||||
- Value: 0x2a
|
||||
|
||||
# 0x20: DW_TAG_pointer_type
|
||||
# DW_AT_type [DW_AT_type [DW_FORM_ref4] (0x00000025 \"void (...)\")
|
||||
- AbbrCode: 0x04
|
||||
Values:
|
||||
- Value: 0x25
|
||||
|
||||
# 0x25: DW_TAG_subroutine_type
|
||||
- AbbrCode: 0x05
|
||||
|
||||
# 0x26: DW_TAG_unspecified_parameters
|
||||
- AbbrCode: 0x06
|
||||
|
||||
- AbbrCode: 0x00 # end of child tags of 0x25
|
||||
- AbbrCode: 0x00 # end of child tags of 0x0c
|
||||
...
|
||||
)";
|
||||
YAMLModuleTester t(yamldata);
|
||||
|
||||
DWARFUnit *unit = t.GetDwarfUnit();
|
||||
ASSERT_NE(unit, nullptr);
|
||||
const DWARFDebugInfoEntry *cu_entry = unit->DIE().GetDIE();
|
||||
ASSERT_EQ(cu_entry->Tag(), DW_TAG_compile_unit);
|
||||
DWARFDIE cu_die(unit, cu_entry);
|
||||
|
||||
auto holder = std::make_unique<clang_utils::TypeSystemClangHolder>("ast");
|
||||
auto &ast_ctx = *holder->GetAST();
|
||||
DWARFASTParserClangStub ast_parser(ast_ctx);
|
||||
|
||||
DWARFDIE ptrauth_variable = cu_die.GetFirstChild();
|
||||
ASSERT_EQ(ptrauth_variable.Tag(), DW_TAG_variable);
|
||||
DWARFDIE ptrauth_type =
|
||||
ptrauth_variable.GetAttributeValueAsReferenceDIE(DW_AT_type);
|
||||
ASSERT_EQ(ptrauth_type.Tag(), DW_TAG_LLVM_ptrauth_type);
|
||||
|
||||
SymbolContext sc;
|
||||
bool new_type = false;
|
||||
lldb::TypeSP type_sp =
|
||||
ast_parser.ParseTypeFromDWARF(sc, ptrauth_type, &new_type);
|
||||
CompilerType compiler_type = type_sp->GetForwardCompilerType();
|
||||
ASSERT_EQ(compiler_type.GetPtrAuthKey(), 0);
|
||||
ASSERT_EQ(compiler_type.GetPtrAuthAddressDiversity(), false);
|
||||
ASSERT_EQ(compiler_type.GetPtrAuthDiscriminator(), 42);
|
||||
}
|
||||
|
||||
struct ExtractIntFromFormValueTest : public testing::Test {
|
||||
SubsystemRAII<FileSystem, HostInfo> subsystems;
|
||||
clang_utils::TypeSystemClangHolder holder;
|
||||
|
||||
Reference in New Issue
Block a user