ld: Parse LF_UDT_SRC_LINE records when creating PDB file
This commit is contained in:
parent
fca9096a94
commit
817840046a
171
ld/pdb.c
171
ld/pdb.c
@ -76,6 +76,7 @@ struct type_entry
|
||||
struct type_entry *next;
|
||||
uint32_t index;
|
||||
uint32_t cv_hash;
|
||||
bool has_udt_src_line;
|
||||
uint8_t data[];
|
||||
};
|
||||
|
||||
@ -709,19 +710,19 @@ copy_filechksms (uint8_t *data, uint32_t size, char *string_table,
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Add a string to the strings table, if it's not already there. */
|
||||
static void
|
||||
/* Add a string to the strings table, if it's not already there. Returns its
|
||||
offset within the string table. */
|
||||
static uint32_t
|
||||
add_string (char *str, size_t len, struct string_table *strings)
|
||||
{
|
||||
uint32_t hash = calc_hash (str, len);
|
||||
struct string *s;
|
||||
void **slot;
|
||||
|
||||
slot = htab_find_slot_with_hash (strings->hashmap, str, hash, INSERT);
|
||||
|
||||
if (!*slot)
|
||||
{
|
||||
struct string *s;
|
||||
|
||||
*slot = xmalloc (offsetof (struct string, s) + len);
|
||||
|
||||
s = (struct string *) *slot;
|
||||
@ -742,6 +743,12 @@ add_string (char *str, size_t len, struct string_table *strings)
|
||||
|
||||
strings->strings_len += len + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
s = (struct string *) *slot;
|
||||
}
|
||||
|
||||
return s->offset;
|
||||
}
|
||||
|
||||
/* Return the hash of an entry in the string table. */
|
||||
@ -1119,13 +1126,149 @@ is_name_anonymous (char *name, size_t len)
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Handle LF_UDT_SRC_LINE type entries, which are a special case. These
|
||||
give the source file and line number for each user-defined type that is
|
||||
declared. We parse these and emit instead an LF_UDT_MOD_SRC_LINE entry,
|
||||
which also includes the module number. */
|
||||
static bool
|
||||
handle_udt_src_line (uint8_t *data, uint16_t size, struct type_entry **map,
|
||||
uint32_t type_num, uint32_t num_types,
|
||||
struct types *ids, uint16_t mod_num,
|
||||
struct string_table *strings)
|
||||
{
|
||||
struct lf_udt_src_line *usl = (struct lf_udt_src_line *) data;
|
||||
uint32_t orig_type, source_file_type;
|
||||
void **slot;
|
||||
hashval_t hash;
|
||||
struct type_entry *e, *type_e, *str_e;
|
||||
struct lf_udt_mod_src_line *umsl;
|
||||
struct lf_string_id *str;
|
||||
uint32_t source_file_offset;
|
||||
|
||||
if (size < sizeof (struct lf_udt_src_line))
|
||||
{
|
||||
einfo (_("%P: warning: truncated CodeView type record"
|
||||
" LF_UDT_SRC_LINE\n"));
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Check if LF_UDT_MOD_SRC_LINE already present for type, and return. */
|
||||
|
||||
orig_type = bfd_getl32 (&usl->type);
|
||||
|
||||
if (orig_type < TPI_FIRST_INDEX ||
|
||||
orig_type >= TPI_FIRST_INDEX + num_types ||
|
||||
!map[orig_type - TPI_FIRST_INDEX])
|
||||
{
|
||||
einfo (_("%P: warning: CodeView type record LF_UDT_SRC_LINE"
|
||||
" referred to unknown type %v\n"), orig_type);
|
||||
return false;
|
||||
}
|
||||
|
||||
type_e = map[orig_type - TPI_FIRST_INDEX];
|
||||
|
||||
/* Skip if type already declared in other module. */
|
||||
if (type_e->has_udt_src_line)
|
||||
return true;
|
||||
|
||||
if (!remap_type (&usl->type, map, type_num, num_types))
|
||||
return false;
|
||||
|
||||
/* Extract string from source_file_type. */
|
||||
|
||||
source_file_type = bfd_getl32 (&usl->source_file_type);
|
||||
|
||||
if (source_file_type < TPI_FIRST_INDEX ||
|
||||
source_file_type >= TPI_FIRST_INDEX + num_types ||
|
||||
!map[source_file_type - TPI_FIRST_INDEX])
|
||||
{
|
||||
einfo (_("%P: warning: CodeView type record LF_UDT_SRC_LINE"
|
||||
" referred to unknown string %v\n"), source_file_type);
|
||||
return false;
|
||||
}
|
||||
|
||||
str_e = map[source_file_type - TPI_FIRST_INDEX];
|
||||
|
||||
if (bfd_getl16 (str_e->data + sizeof (uint16_t)) != LF_STRING_ID)
|
||||
{
|
||||
einfo (_("%P: warning: CodeView type record LF_UDT_SRC_LINE"
|
||||
" pointed to unexpected record type\n"));
|
||||
return false;
|
||||
}
|
||||
|
||||
str = (struct lf_string_id *) str_e->data;
|
||||
|
||||
/* Add string to string table. */
|
||||
|
||||
source_file_offset = add_string (str->string, strlen (str->string),
|
||||
strings);
|
||||
|
||||
/* Add LF_UDT_MOD_SRC_LINE entry. */
|
||||
|
||||
size = sizeof (struct lf_udt_mod_src_line);
|
||||
|
||||
e = xmalloc (offsetof (struct type_entry, data) + size);
|
||||
|
||||
e->next = NULL;
|
||||
e->index = ids->num_types;
|
||||
e->has_udt_src_line = false;
|
||||
|
||||
/* LF_UDT_MOD_SRC_LINE use calc_hash on the type number, rather than
|
||||
the crc32 used for type hashes elsewhere. */
|
||||
e->cv_hash = calc_hash ((char *) &usl->type, sizeof (uint32_t));
|
||||
|
||||
type_e->has_udt_src_line = true;
|
||||
|
||||
umsl = (struct lf_udt_mod_src_line *) e->data;
|
||||
|
||||
bfd_putl16 (size - sizeof (uint16_t), &umsl->size);
|
||||
bfd_putl16 (LF_UDT_MOD_SRC_LINE, &umsl->kind);
|
||||
memcpy (&umsl->type, &usl->type, sizeof (uint32_t));
|
||||
bfd_putl32 (source_file_offset, &umsl->source_file_string);
|
||||
memcpy (&umsl->line_no, &usl->line_no, sizeof (uint32_t));
|
||||
bfd_putl16 (mod_num + 1, &umsl->module_no);
|
||||
|
||||
hash = iterative_hash (e->data, size, 0);
|
||||
|
||||
slot = htab_find_slot_with_hash (ids->hashmap, data, hash, INSERT);
|
||||
if (!slot)
|
||||
{
|
||||
free (e);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (*slot)
|
||||
{
|
||||
free (e);
|
||||
einfo (_("%P: warning: duplicate CodeView type record "
|
||||
"LF_UDT_MOD_SRC_LINE\n"));
|
||||
return false;
|
||||
}
|
||||
|
||||
*slot = e;
|
||||
|
||||
if (ids->last)
|
||||
ids->last->next = e;
|
||||
else
|
||||
ids->first = e;
|
||||
|
||||
ids->last = e;
|
||||
|
||||
map[type_num] = e;
|
||||
|
||||
ids->num_types++;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Parse a type definition in the .debug$T section. We remap the numbers
|
||||
of any referenced types, and if the type is not a duplicate of one
|
||||
already seen add it to types (for TPI types) or ids (for IPI types). */
|
||||
static bool
|
||||
handle_type (uint8_t *data, struct type_entry **map, uint32_t type_num,
|
||||
uint32_t num_types, struct types *types,
|
||||
struct types *ids)
|
||||
struct types *ids, uint16_t mod_num,
|
||||
struct string_table *strings)
|
||||
{
|
||||
uint16_t size, type;
|
||||
void **slot;
|
||||
@ -2076,6 +2219,10 @@ handle_type (uint8_t *data, struct type_entry **map, uint32_t type_num,
|
||||
break;
|
||||
}
|
||||
|
||||
case LF_UDT_SRC_LINE:
|
||||
return handle_udt_src_line (data, size, map, type_num, num_types,
|
||||
ids, mod_num, strings);
|
||||
|
||||
default:
|
||||
einfo (_("%P: warning: unrecognized CodeView type %v\n"), type);
|
||||
return false;
|
||||
@ -2105,6 +2252,8 @@ handle_type (uint8_t *data, struct type_entry **map, uint32_t type_num,
|
||||
else
|
||||
e->cv_hash = crc32 (data, size);
|
||||
|
||||
e->has_udt_src_line = false;
|
||||
|
||||
memcpy (e->data, data, size);
|
||||
|
||||
if (t->last)
|
||||
@ -2130,7 +2279,8 @@ handle_type (uint8_t *data, struct type_entry **map, uint32_t type_num,
|
||||
found to handle_type. */
|
||||
static bool
|
||||
handle_debugt_section (asection *s, bfd *mod, struct types *types,
|
||||
struct types *ids)
|
||||
struct types *ids, uint16_t mod_num,
|
||||
struct string_table *strings)
|
||||
{
|
||||
bfd_byte *data = NULL;
|
||||
size_t off;
|
||||
@ -2187,7 +2337,8 @@ handle_debugt_section (asection *s, bfd *mod, struct types *types,
|
||||
|
||||
size = bfd_getl16 (data + off);
|
||||
|
||||
if (!handle_type (data + off, map, type_num, num_types, types, ids))
|
||||
if (!handle_type (data + off, map, type_num, num_types, types, ids,
|
||||
mod_num, strings))
|
||||
{
|
||||
free (data);
|
||||
free (map);
|
||||
@ -2213,7 +2364,7 @@ populate_module_stream (bfd *stream, bfd *mod, uint32_t *sym_byte_size,
|
||||
uint32_t *c13_info_size,
|
||||
struct mod_source_files *mod_source,
|
||||
bfd *abfd, struct types *types,
|
||||
struct types *ids)
|
||||
struct types *ids, uint16_t mod_num)
|
||||
{
|
||||
uint8_t int_buf[sizeof (uint32_t)];
|
||||
uint8_t *c13_info = NULL;
|
||||
@ -2237,7 +2388,7 @@ populate_module_stream (bfd *stream, bfd *mod, uint32_t *sym_byte_size,
|
||||
}
|
||||
else if (!strcmp (s->name, ".debug$T") && s->size >= sizeof (uint32_t))
|
||||
{
|
||||
if (!handle_debugt_section (s, mod, types, ids))
|
||||
if (!handle_debugt_section (s, mod, types, ids, mod_num, strings))
|
||||
{
|
||||
free (c13_info);
|
||||
free (mod_source->files);
|
||||
@ -2371,7 +2522,7 @@ create_module_info_substream (bfd *abfd, bfd *pdb, void **data,
|
||||
if (!populate_module_stream (stream, in, &sym_byte_size,
|
||||
strings, &c13_info_size,
|
||||
&source->mods[mod_num], abfd,
|
||||
types, ids))
|
||||
types, ids, mod_num))
|
||||
{
|
||||
for (unsigned int i = 0; i < source->mod_count; i++)
|
||||
{
|
||||
|
23
ld/pdb.h
23
ld/pdb.h
@ -59,6 +59,8 @@
|
||||
#define LF_BUILDINFO 0x1603
|
||||
#define LF_SUBSTR_LIST 0x1604
|
||||
#define LF_STRING_ID 0x1605
|
||||
#define LF_UDT_SRC_LINE 0x1606
|
||||
#define LF_UDT_MOD_SRC_LINE 0x1607
|
||||
|
||||
#define LF_CHAR 0x8000
|
||||
#define LF_SHORT 0x8001
|
||||
@ -517,6 +519,27 @@ struct lf_mfunc_id
|
||||
char name[];
|
||||
} ATTRIBUTE_PACKED;
|
||||
|
||||
/* lfUdtSrcLine in cvinfo.h */
|
||||
struct lf_udt_src_line
|
||||
{
|
||||
uint16_t size;
|
||||
uint16_t kind;
|
||||
uint32_t type;
|
||||
uint32_t source_file_type;
|
||||
uint32_t line_no;
|
||||
} ATTRIBUTE_PACKED;
|
||||
|
||||
/* lfUdtModSrcLine in cvinfo.h */
|
||||
struct lf_udt_mod_src_line
|
||||
{
|
||||
uint16_t size;
|
||||
uint16_t kind;
|
||||
uint32_t type;
|
||||
uint32_t source_file_string;
|
||||
uint32_t line_no;
|
||||
uint16_t module_no;
|
||||
} ATTRIBUTE_PACKED;
|
||||
|
||||
extern bool create_pdb_file (bfd *, const char *, const unsigned char *);
|
||||
|
||||
#endif
|
||||
|
5
ld/testsuite/ld-pe/pdb-types3-hashlist.d
Normal file
5
ld/testsuite/ld-pe/pdb-types3-hashlist.d
Normal file
@ -0,0 +1,5 @@
|
||||
|
||||
*: file format binary
|
||||
|
||||
Contents of section .data:
|
||||
0000 d4d90000 0c1c0000 *
|
5
ld/testsuite/ld-pe/pdb-types3-skiplist.d
Normal file
5
ld/testsuite/ld-pe/pdb-types3-skiplist.d
Normal file
@ -0,0 +1,5 @@
|
||||
|
||||
*: file format binary
|
||||
|
||||
Contents of section .data:
|
||||
0000 00100000 00000000 *
|
7
ld/testsuite/ld-pe/pdb-types3-typelist.d
Normal file
7
ld/testsuite/ld-pe/pdb-types3-typelist.d
Normal file
@ -0,0 +1,7 @@
|
||||
|
||||
*: file format binary
|
||||
|
||||
Contents of section .data:
|
||||
0000 0e000516 00000000 666f6f2e 6800f2f1 ........foo.h...
|
||||
0010 10000716 01100000 01000000 2a000000 ............*...
|
||||
0020 0100 ..
|
57
ld/testsuite/ld-pe/pdb-types3a.s
Normal file
57
ld/testsuite/ld-pe/pdb-types3a.s
Normal file
@ -0,0 +1,57 @@
|
||||
.equ CV_SIGNATURE_C13, 4
|
||||
.equ T_INT4, 0x0074
|
||||
|
||||
.equ LF_FIELDLIST, 0x1203
|
||||
.equ LF_STRUCTURE, 0x1505
|
||||
.equ LF_MEMBER, 0x150d
|
||||
.equ LF_STRING_ID, 0x1605
|
||||
.equ LF_UDT_SRC_LINE, 0x1606
|
||||
|
||||
.section ".debug$T", "rn"
|
||||
|
||||
.long CV_SIGNATURE_C13
|
||||
|
||||
# Type 1000, fieldlist for struct foo
|
||||
.fieldlist1:
|
||||
.short .struct1 - .fieldlist1 - 2
|
||||
.short LF_FIELDLIST
|
||||
.short LF_MEMBER
|
||||
.short 3 # public
|
||||
.long T_INT4
|
||||
.short 0 # offset
|
||||
.asciz "num"
|
||||
.byte 0xf2 # padding
|
||||
.byte 0xf1 # padding
|
||||
|
||||
# Type 1001, struct foo
|
||||
.struct1:
|
||||
.short .string1 - .struct1 - 2
|
||||
.short LF_STRUCTURE
|
||||
.short 1 # no. members
|
||||
.short 0 # property
|
||||
.long 0x1000 # field list
|
||||
.long 0 # type derived from
|
||||
.long 0 # type of vshape table
|
||||
.short 4 # size
|
||||
.asciz "foo" # name
|
||||
.byte 0xf2 # padding
|
||||
.byte 0xf1 # padding
|
||||
|
||||
# Type 1002, string "foo"
|
||||
.string1:
|
||||
.short .udtsrcline1 - .string1 - 2
|
||||
.short LF_STRING_ID
|
||||
.long 0 # sub-string
|
||||
.asciz "foo.h"
|
||||
.byte 0xf2
|
||||
.byte 0xf1
|
||||
|
||||
# Type 1003, UDT source line for type 1001
|
||||
.udtsrcline1:
|
||||
.short .types_end - .udtsrcline1 - 2
|
||||
.short LF_UDT_SRC_LINE
|
||||
.long 0x1001
|
||||
.long 0x1002 # source file string
|
||||
.long 42 # line no.
|
||||
|
||||
.types_end:
|
68
ld/testsuite/ld-pe/pdb-types3b.s
Normal file
68
ld/testsuite/ld-pe/pdb-types3b.s
Normal file
@ -0,0 +1,68 @@
|
||||
.equ CV_SIGNATURE_C13, 4
|
||||
|
||||
.equ T_LONG, 0x0012
|
||||
.equ T_INT4, 0x0074
|
||||
|
||||
.equ LF_MODIFIER, 0x1001
|
||||
.equ LF_FIELDLIST, 0x1203
|
||||
.equ LF_STRUCTURE, 0x1505
|
||||
.equ LF_MEMBER, 0x150d
|
||||
.equ LF_STRING_ID, 0x1605
|
||||
.equ LF_UDT_SRC_LINE, 0x1606
|
||||
|
||||
.section ".debug$T", "rn"
|
||||
|
||||
.long CV_SIGNATURE_C13
|
||||
|
||||
# Type 1000, const long
|
||||
.mod1:
|
||||
.short .fieldlist1 - .mod1 - 2
|
||||
.short LF_MODIFIER
|
||||
.long T_LONG
|
||||
.short 1 # const
|
||||
.short 0 # padding
|
||||
|
||||
# Type 1001, fieldlist for struct foo
|
||||
.fieldlist1:
|
||||
.short .struct1 - .fieldlist1 - 2
|
||||
.short LF_FIELDLIST
|
||||
.short LF_MEMBER
|
||||
.short 3 # public
|
||||
.long T_INT4
|
||||
.short 0 # offset
|
||||
.asciz "num"
|
||||
.byte 0xf2 # padding
|
||||
.byte 0xf1 # padding
|
||||
|
||||
# Type 1002, struct foo
|
||||
.struct1:
|
||||
.short .string1 - .struct1 - 2
|
||||
.short LF_STRUCTURE
|
||||
.short 1 # no. members
|
||||
.short 0 # property
|
||||
.long 0x1001 # field list
|
||||
.long 0 # type derived from
|
||||
.long 0 # type of vshape table
|
||||
.short 4 # size
|
||||
.asciz "foo" # name
|
||||
.byte 0xf2 # padding
|
||||
.byte 0xf1 # padding
|
||||
|
||||
# Type 1003, string "foo"
|
||||
.string1:
|
||||
.short .udtsrcline1 - .string1 - 2
|
||||
.short LF_STRING_ID
|
||||
.long 0 # sub-string
|
||||
.asciz "foo.h"
|
||||
.byte 0xf2
|
||||
.byte 0xf1
|
||||
|
||||
# Type 1004, UDT source line for type 1002
|
||||
.udtsrcline1:
|
||||
.short .types_end - .udtsrcline1 - 2
|
||||
.short LF_UDT_SRC_LINE
|
||||
.long 0x1002
|
||||
.long 0x1003 # source file string
|
||||
.long 42 # line no.
|
||||
|
||||
.types_end:
|
@ -1319,9 +1319,142 @@ proc test6 { } {
|
||||
}
|
||||
}
|
||||
|
||||
proc test7 { } {
|
||||
global as
|
||||
global ar
|
||||
global ld
|
||||
global objdump
|
||||
global srcdir
|
||||
global subdir
|
||||
|
||||
if ![ld_assemble $as $srcdir/$subdir/pdb-types3a.s tmpdir/pdb-types3a.o] {
|
||||
unsupported "Build pdb-types3a.o"
|
||||
return
|
||||
}
|
||||
|
||||
if ![ld_assemble $as $srcdir/$subdir/pdb-types3b.s tmpdir/pdb-types3b.o] {
|
||||
unsupported "Build pdb-types3b.o"
|
||||
return
|
||||
}
|
||||
|
||||
if ![ld_link $ld "tmpdir/pdb-types3.exe" "--pdb=tmpdir/pdb-types3.pdb tmpdir/pdb-types3a.o tmpdir/pdb-types3b.o"] {
|
||||
unsupported "Create PE image with PDB file"
|
||||
return
|
||||
}
|
||||
|
||||
set exec_output [run_host_cmd "$ar" "x --output tmpdir tmpdir/pdb-types3.pdb 0004"]
|
||||
|
||||
if ![string match "" $exec_output] {
|
||||
fail "Could not extract IPI stream"
|
||||
return
|
||||
} else {
|
||||
pass "Extracted IPI stream"
|
||||
}
|
||||
|
||||
set fi [open tmpdir/0004]
|
||||
fconfigure $fi -translation binary
|
||||
|
||||
seek $fi 16 current
|
||||
|
||||
set data [read $fi 4]
|
||||
binary scan $data i type_list_size
|
||||
|
||||
set data [read $fi 2]
|
||||
binary scan $data s hash_stream_index
|
||||
|
||||
seek $fi 10 current
|
||||
|
||||
set data [read $fi 4]
|
||||
binary scan $data i hash_list_offset
|
||||
|
||||
set data [read $fi 4]
|
||||
binary scan $data i hash_list_size
|
||||
|
||||
set data [read $fi 4]
|
||||
binary scan $data i skip_list_offset
|
||||
|
||||
set data [read $fi 4]
|
||||
binary scan $data i skip_list_size
|
||||
|
||||
seek $fi 8 current
|
||||
|
||||
set type_list [read $fi $type_list_size]
|
||||
|
||||
close $fi
|
||||
|
||||
set fi [open tmpdir/pdb-types3-typelist w]
|
||||
fconfigure $fi -translation binary
|
||||
puts -nonewline $fi $type_list
|
||||
close $fi
|
||||
|
||||
# check type list
|
||||
|
||||
set exp [file_contents "$srcdir/$subdir/pdb-types3-typelist.d"]
|
||||
set got [run_host_cmd "$objdump" "-s --target=binary tmpdir/pdb-types3-typelist"]
|
||||
if ![string match $exp $got] {
|
||||
fail "Incorrect type list in IPI stream."
|
||||
} else {
|
||||
pass "Correct type list in IPI stream."
|
||||
}
|
||||
|
||||
# extract hash list and skip list
|
||||
|
||||
set index_str [format "%04x" $hash_stream_index]
|
||||
|
||||
set exec_output [run_host_cmd "$ar" "x --output tmpdir tmpdir/pdb-types3.pdb $index_str"]
|
||||
|
||||
if ![string match "" $exec_output] {
|
||||
fail "Could not extract IPI hash stream."
|
||||
} else {
|
||||
pass "Extracted IPI hash stream."
|
||||
}
|
||||
|
||||
set fi [open tmpdir/$index_str]
|
||||
fconfigure $fi -translation binary
|
||||
|
||||
seek $fi $hash_list_offset
|
||||
set hash_list [read $fi $hash_list_size]
|
||||
|
||||
seek $fi $skip_list_offset
|
||||
set skip_list [read $fi $skip_list_size]
|
||||
|
||||
close $fi
|
||||
|
||||
# check hash list
|
||||
|
||||
set fi [open tmpdir/pdb-types3-hashlist w]
|
||||
fconfigure $fi -translation binary
|
||||
puts -nonewline $fi $hash_list
|
||||
close $fi
|
||||
|
||||
set exp [file_contents "$srcdir/$subdir/pdb-types3-hashlist.d"]
|
||||
set got [run_host_cmd "$objdump" "-s --target=binary tmpdir/pdb-types3-hashlist"]
|
||||
if ![string match $exp $got] {
|
||||
fail "Incorrect hash list in IPI stream."
|
||||
} else {
|
||||
pass "Correct hash list in IPI stream."
|
||||
}
|
||||
|
||||
# check skip list
|
||||
|
||||
set fi [open tmpdir/pdb-types3-skiplist w]
|
||||
fconfigure $fi -translation binary
|
||||
puts -nonewline $fi $skip_list
|
||||
close $fi
|
||||
|
||||
set exp [file_contents "$srcdir/$subdir/pdb-types3-skiplist.d"]
|
||||
set got [run_host_cmd "$objdump" "-s --target=binary tmpdir/pdb-types3-skiplist"]
|
||||
if ![string match $exp $got] {
|
||||
fail "Incorrect skip list in IPI stream."
|
||||
} else {
|
||||
pass "Correct skip list in IPI stream."
|
||||
}
|
||||
}
|
||||
|
||||
test1
|
||||
test2
|
||||
test3
|
||||
test4
|
||||
test5
|
||||
test6
|
||||
test7
|
||||
|
Loading…
x
Reference in New Issue
Block a user