updated gdtr/idtr/ldtr/tr read/write code
This commit is contained in:
parent
9977054a15
commit
e59382e030
|
@ -8,6 +8,15 @@
|
|||
extern "C" {
|
||||
#endif
|
||||
|
||||
//Memory-Management Register fields (idtr, gdtr, ldtr, tr)
|
||||
//borrow from SegmentCache in qemu/target-i386/cpu.h
|
||||
typedef struct x86_mmr {
|
||||
uint32_t selector;
|
||||
uint64_t base; /* handle 32 or 64 bit CPUs */
|
||||
uint32_t limit;
|
||||
uint32_t flags;
|
||||
} x86_mmr;
|
||||
|
||||
// Callback function for tracing SYSCALL/SYSENTER (for uc_hook_intr())
|
||||
// @user_data: user data passed to tracing APIs.
|
||||
typedef void (*uc_cb_insn_syscall_t)(struct uc_struct *uc, void *user_data);
|
||||
|
@ -64,9 +73,7 @@ typedef enum uc_x86_reg {
|
|||
UC_X86_REG_R9D, UC_X86_REG_R10D, UC_X86_REG_R11D, UC_X86_REG_R12D, UC_X86_REG_R13D,
|
||||
UC_X86_REG_R14D, UC_X86_REG_R15D, UC_X86_REG_R8W, UC_X86_REG_R9W, UC_X86_REG_R10W,
|
||||
UC_X86_REG_R11W, UC_X86_REG_R12W, UC_X86_REG_R13W, UC_X86_REG_R14W, UC_X86_REG_R15W,
|
||||
UC_X86_REG_IDTR_LIMIT, UC_X86_REG_IDTR_BASE, UC_X86_REG_GDTR_LIMIT, UC_X86_REG_GDTR_BASE,
|
||||
UC_X86_REG_LDTR_SS, UC_X86_REG_LDTR_LIMIT, UC_X86_REG_LDTR_BASE, UC_X86_REG_LDTR_ATTR,
|
||||
UC_X86_REG_TR_SS, UC_X86_REG_TR_LIMIT, UC_X86_REG_TR_BASE, UC_X86_REG_TR_ATTR,
|
||||
UC_X86_REG_IDTR, UC_X86_REG_GDTR, UC_X86_REG_LDTR, UC_X86_REG_TR,
|
||||
|
||||
UC_X86_REG_ENDING // <-- mark the end of the list of registers
|
||||
} uc_x86_reg;
|
||||
|
|
|
@ -699,7 +699,7 @@ typedef enum {
|
|||
|
||||
typedef struct SegmentCache {
|
||||
uint32_t selector;
|
||||
target_ulong base;
|
||||
uint64_t base; /* handle 32 or 64 bit CPUs */
|
||||
uint32_t limit;
|
||||
uint32_t flags;
|
||||
} SegmentCache;
|
||||
|
|
|
@ -277,41 +277,25 @@ int x86_reg_read(struct uc_struct *uc, unsigned int regid, void *value)
|
|||
case UC_X86_REG_GS:
|
||||
*(int32_t *)value = X86_CPU(uc, mycpu)->env.segs[R_GS].base;
|
||||
break;
|
||||
case UC_X86_REG_IDTR_LIMIT:
|
||||
*(int16_t *)value = READ_WORD(X86_CPU(uc, mycpu)->env.idt.limit);
|
||||
case UC_X86_REG_IDTR:
|
||||
((SegmentCache *)value)->limit = (uint16_t)X86_CPU(uc, mycpu)->env.idt.limit;
|
||||
((SegmentCache *)value)->base = (uint32_t)X86_CPU(uc, mycpu)->env.idt.base;
|
||||
break;
|
||||
case UC_X86_REG_IDTR_BASE:
|
||||
*(int32_t *)value = X86_CPU(uc, mycpu)->env.idt.base;
|
||||
case UC_X86_REG_GDTR:
|
||||
((SegmentCache *)value)->limit = (uint16_t)X86_CPU(uc, mycpu)->env.gdt.limit;
|
||||
((SegmentCache *)value)->base = (uint32_t)X86_CPU(uc, mycpu)->env.gdt.base;
|
||||
break;
|
||||
case UC_X86_REG_GDTR_LIMIT:
|
||||
*(int16_t *)value = READ_WORD(X86_CPU(uc, mycpu)->env.gdt.limit);
|
||||
case UC_X86_REG_LDTR:
|
||||
((SegmentCache *)value)->limit = X86_CPU(uc, mycpu)->env.ldt.limit;
|
||||
((SegmentCache *)value)->base = (uint32_t)X86_CPU(uc, mycpu)->env.ldt.base;
|
||||
((SegmentCache *)value)->selector = (uint16_t)X86_CPU(uc, mycpu)->env.ldt.selector;
|
||||
((SegmentCache *)value)->flags = X86_CPU(uc, mycpu)->env.ldt.flags;
|
||||
break;
|
||||
case UC_X86_REG_GDTR_BASE:
|
||||
*(int32_t *)value = X86_CPU(uc, mycpu)->env.gdt.base;
|
||||
break;
|
||||
case UC_X86_REG_LDTR_SS:
|
||||
*(int16_t *)value = READ_WORD(X86_CPU(uc, mycpu)->env.ldt.selector);
|
||||
break;
|
||||
case UC_X86_REG_LDTR_LIMIT:
|
||||
*(int32_t *)value = X86_CPU(uc, mycpu)->env.ldt.limit;
|
||||
break;
|
||||
case UC_X86_REG_LDTR_BASE:
|
||||
*(int32_t *)value = X86_CPU(uc, mycpu)->env.ldt.base;
|
||||
break;
|
||||
case UC_X86_REG_LDTR_ATTR:
|
||||
*(int32_t *)value = X86_CPU(uc, mycpu)->env.ldt.flags;
|
||||
break;
|
||||
case UC_X86_REG_TR_SS:
|
||||
*(int16_t *)value = READ_WORD(X86_CPU(uc, mycpu)->env.tr.selector);
|
||||
break;
|
||||
case UC_X86_REG_TR_LIMIT:
|
||||
*(int32_t *)value = X86_CPU(uc, mycpu)->env.tr.limit;
|
||||
break;
|
||||
case UC_X86_REG_TR_BASE:
|
||||
*(int32_t *)value = X86_CPU(uc, mycpu)->env.tr.base;
|
||||
break;
|
||||
case UC_X86_REG_TR_ATTR:
|
||||
*(int32_t *)value = X86_CPU(uc, mycpu)->env.tr.flags;
|
||||
case UC_X86_REG_TR:
|
||||
((SegmentCache *)value)->limit = X86_CPU(uc, mycpu)->env.tr.limit;
|
||||
((SegmentCache *)value)->base = (uint32_t)X86_CPU(uc, mycpu)->env.tr.base;
|
||||
((SegmentCache *)value)->selector = (uint16_t)X86_CPU(uc, mycpu)->env.tr.selector;
|
||||
((SegmentCache *)value)->flags = X86_CPU(uc, mycpu)->env.tr.flags;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
@ -561,41 +545,25 @@ int x86_reg_read(struct uc_struct *uc, unsigned int regid, void *value)
|
|||
case UC_X86_REG_R15B:
|
||||
*(int8_t *)value = READ_BYTE_L(X86_CPU(uc, mycpu)->env.regs[15]);
|
||||
break;
|
||||
case UC_X86_REG_IDTR_LIMIT:
|
||||
*(int16_t *)value = READ_WORD(X86_CPU(uc, mycpu)->env.idt.limit);
|
||||
case UC_X86_REG_IDTR:
|
||||
((SegmentCache *)value)->limit = (uint16_t)X86_CPU(uc, mycpu)->env.idt.limit;
|
||||
((SegmentCache *)value)->base = X86_CPU(uc, mycpu)->env.idt.base;
|
||||
break;
|
||||
case UC_X86_REG_IDTR_BASE:
|
||||
*(int64_t *)value = X86_CPU(uc, mycpu)->env.idt.base;
|
||||
case UC_X86_REG_GDTR:
|
||||
((SegmentCache *)value)->limit = (uint16_t)X86_CPU(uc, mycpu)->env.gdt.limit;
|
||||
((SegmentCache *)value)->base = X86_CPU(uc, mycpu)->env.gdt.base;
|
||||
break;
|
||||
case UC_X86_REG_GDTR_LIMIT:
|
||||
*(int16_t *)value = READ_WORD(X86_CPU(uc, mycpu)->env.gdt.limit);
|
||||
case UC_X86_REG_LDTR:
|
||||
((SegmentCache *)value)->limit = X86_CPU(uc, mycpu)->env.ldt.limit;
|
||||
((SegmentCache *)value)->base = X86_CPU(uc, mycpu)->env.ldt.base;
|
||||
((SegmentCache *)value)->selector = (uint16_t)X86_CPU(uc, mycpu)->env.ldt.selector;
|
||||
((SegmentCache *)value)->flags = X86_CPU(uc, mycpu)->env.ldt.flags;
|
||||
break;
|
||||
case UC_X86_REG_GDTR_BASE:
|
||||
*(int64_t *)value = X86_CPU(uc, mycpu)->env.gdt.base;
|
||||
break;
|
||||
case UC_X86_REG_LDTR_SS:
|
||||
*(int16_t *)value = READ_WORD(X86_CPU(uc, mycpu)->env.ldt.selector);
|
||||
break;
|
||||
case UC_X86_REG_LDTR_LIMIT:
|
||||
*(int32_t *)value = X86_CPU(uc, mycpu)->env.ldt.limit;
|
||||
break;
|
||||
case UC_X86_REG_LDTR_BASE:
|
||||
*(int64_t *)value = X86_CPU(uc, mycpu)->env.ldt.base;
|
||||
break;
|
||||
case UC_X86_REG_LDTR_ATTR:
|
||||
*(int32_t *)value = X86_CPU(uc, mycpu)->env.ldt.flags;
|
||||
break;
|
||||
case UC_X86_REG_TR_SS:
|
||||
*(int16_t *)value = READ_WORD(X86_CPU(uc, mycpu)->env.tr.selector);
|
||||
break;
|
||||
case UC_X86_REG_TR_LIMIT:
|
||||
*(int32_t *)value = X86_CPU(uc, mycpu)->env.tr.limit;
|
||||
break;
|
||||
case UC_X86_REG_TR_BASE:
|
||||
*(int64_t *)value = X86_CPU(uc, mycpu)->env.tr.base;
|
||||
break;
|
||||
case UC_X86_REG_TR_ATTR:
|
||||
*(int32_t *)value = X86_CPU(uc, mycpu)->env.tr.flags;
|
||||
case UC_X86_REG_TR:
|
||||
((SegmentCache *)value)->limit = X86_CPU(uc, mycpu)->env.tr.limit;
|
||||
((SegmentCache *)value)->base = X86_CPU(uc, mycpu)->env.tr.base;
|
||||
((SegmentCache *)value)->selector = (uint16_t)X86_CPU(uc, mycpu)->env.tr.selector;
|
||||
((SegmentCache *)value)->flags = X86_CPU(uc, mycpu)->env.tr.flags;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
@ -756,41 +724,25 @@ int x86_reg_write(struct uc_struct *uc, unsigned int regid, const void *value)
|
|||
case UC_X86_REG_GS:
|
||||
X86_CPU(uc, mycpu)->env.segs[R_GS].base = *(uint32_t *)value;
|
||||
break;
|
||||
case UC_X86_REG_IDTR_LIMIT:
|
||||
WRITE_WORD(X86_CPU(uc, mycpu)->env.idt.limit, *(uint16_t *)value);
|
||||
case UC_X86_REG_IDTR:
|
||||
X86_CPU(uc, mycpu)->env.idt.limit = (uint16_t)((SegmentCache *)value)->limit;
|
||||
X86_CPU(uc, mycpu)->env.idt.base = (uint32_t)((SegmentCache *)value)->base;
|
||||
break;
|
||||
case UC_X86_REG_IDTR_BASE:
|
||||
X86_CPU(uc, mycpu)->env.idt.base = *(uint32_t *)value;
|
||||
case UC_X86_REG_GDTR:
|
||||
X86_CPU(uc, mycpu)->env.gdt.limit = (uint16_t)((SegmentCache *)value)->limit;
|
||||
X86_CPU(uc, mycpu)->env.gdt.base = (uint32_t)((SegmentCache *)value)->base;
|
||||
break;
|
||||
case UC_X86_REG_GDTR_LIMIT:
|
||||
WRITE_WORD(X86_CPU(uc, mycpu)->env.gdt.limit, *(uint16_t *)value);
|
||||
case UC_X86_REG_LDTR:
|
||||
X86_CPU(uc, mycpu)->env.ldt.limit = ((SegmentCache *)value)->limit;
|
||||
X86_CPU(uc, mycpu)->env.ldt.base = (uint32_t)((SegmentCache *)value)->base;
|
||||
X86_CPU(uc, mycpu)->env.ldt.selector = (uint16_t)((SegmentCache *)value)->selector;
|
||||
X86_CPU(uc, mycpu)->env.ldt.flags = ((SegmentCache *)value)->flags;
|
||||
break;
|
||||
case UC_X86_REG_GDTR_BASE:
|
||||
X86_CPU(uc, mycpu)->env.gdt.base = *(uint32_t *)value;
|
||||
break;
|
||||
case UC_X86_REG_LDTR_SS:
|
||||
WRITE_WORD(X86_CPU(uc, mycpu)->env.ldt.selector, *(uint16_t *)value);
|
||||
break;
|
||||
case UC_X86_REG_LDTR_LIMIT:
|
||||
X86_CPU(uc, mycpu)->env.ldt.limit = *(uint32_t *)value;
|
||||
break;
|
||||
case UC_X86_REG_LDTR_BASE:
|
||||
X86_CPU(uc, mycpu)->env.ldt.base = *(uint32_t *)value;
|
||||
break;
|
||||
case UC_X86_REG_LDTR_ATTR:
|
||||
X86_CPU(uc, mycpu)->env.ldt.flags = *(uint32_t *)value;
|
||||
break;
|
||||
case UC_X86_REG_TR_SS:
|
||||
WRITE_WORD(X86_CPU(uc, mycpu)->env.tr.selector, *(uint16_t *)value);
|
||||
break;
|
||||
case UC_X86_REG_TR_LIMIT:
|
||||
X86_CPU(uc, mycpu)->env.tr.limit = *(uint32_t *)value;
|
||||
break;
|
||||
case UC_X86_REG_TR_BASE:
|
||||
X86_CPU(uc, mycpu)->env.tr.base = *(uint32_t *)value;
|
||||
break;
|
||||
case UC_X86_REG_TR_ATTR:
|
||||
X86_CPU(uc, mycpu)->env.tr.flags = *(uint32_t *)value;
|
||||
case UC_X86_REG_TR:
|
||||
X86_CPU(uc, mycpu)->env.tr.limit = ((SegmentCache *)value)->limit;
|
||||
X86_CPU(uc, mycpu)->env.tr.base = (uint32_t)((SegmentCache *)value)->base;
|
||||
X86_CPU(uc, mycpu)->env.tr.selector = (uint16_t)((SegmentCache *)value)->selector;
|
||||
X86_CPU(uc, mycpu)->env.tr.flags = ((SegmentCache *)value)->flags;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
@ -1050,41 +1002,25 @@ int x86_reg_write(struct uc_struct *uc, unsigned int regid, const void *value)
|
|||
case UC_X86_REG_R15B:
|
||||
WRITE_BYTE_L(X86_CPU(uc, mycpu)->env.regs[15], *(uint8_t *)value);
|
||||
break;
|
||||
case UC_X86_REG_IDTR_LIMIT:
|
||||
WRITE_WORD(X86_CPU(uc, mycpu)->env.idt.limit, *(uint16_t *)value);
|
||||
case UC_X86_REG_IDTR:
|
||||
X86_CPU(uc, mycpu)->env.idt.limit = (uint16_t)((SegmentCache *)value)->limit;
|
||||
X86_CPU(uc, mycpu)->env.idt.base = ((SegmentCache *)value)->base;
|
||||
break;
|
||||
case UC_X86_REG_IDTR_BASE:
|
||||
X86_CPU(uc, mycpu)->env.idt.base = *(uint64_t *)value;
|
||||
case UC_X86_REG_GDTR:
|
||||
X86_CPU(uc, mycpu)->env.gdt.limit = (uint16_t)((SegmentCache *)value)->limit;
|
||||
X86_CPU(uc, mycpu)->env.gdt.base = ((SegmentCache *)value)->base;
|
||||
break;
|
||||
case UC_X86_REG_GDTR_LIMIT:
|
||||
WRITE_WORD(X86_CPU(uc, mycpu)->env.gdt.limit, *(uint16_t *)value);
|
||||
case UC_X86_REG_LDTR:
|
||||
X86_CPU(uc, mycpu)->env.ldt.limit = ((SegmentCache *)value)->limit;
|
||||
X86_CPU(uc, mycpu)->env.ldt.base = ((SegmentCache *)value)->base;
|
||||
X86_CPU(uc, mycpu)->env.ldt.selector = (uint16_t)((SegmentCache *)value)->selector;
|
||||
X86_CPU(uc, mycpu)->env.ldt.flags = ((SegmentCache *)value)->flags;
|
||||
break;
|
||||
case UC_X86_REG_GDTR_BASE:
|
||||
X86_CPU(uc, mycpu)->env.gdt.base = *(uint64_t *)value;
|
||||
break;
|
||||
case UC_X86_REG_LDTR_SS:
|
||||
WRITE_WORD(X86_CPU(uc, mycpu)->env.ldt.selector, *(uint16_t *)value);
|
||||
break;
|
||||
case UC_X86_REG_LDTR_LIMIT:
|
||||
WRITE_DWORD(X86_CPU(uc, mycpu)->env.ldt.limit, *(uint32_t *)value);
|
||||
break;
|
||||
case UC_X86_REG_LDTR_BASE:
|
||||
X86_CPU(uc, mycpu)->env.ldt.base = *(uint64_t *)value;
|
||||
break;
|
||||
case UC_X86_REG_LDTR_ATTR:
|
||||
WRITE_DWORD(X86_CPU(uc, mycpu)->env.ldt.flags, *(uint32_t *)value);
|
||||
break;
|
||||
case UC_X86_REG_TR_SS:
|
||||
WRITE_WORD(X86_CPU(uc, mycpu)->env.tr.selector, *(uint16_t *)value);
|
||||
break;
|
||||
case UC_X86_REG_TR_LIMIT:
|
||||
WRITE_DWORD(X86_CPU(uc, mycpu)->env.tr.limit, *(uint32_t *)value);
|
||||
break;
|
||||
case UC_X86_REG_TR_BASE:
|
||||
X86_CPU(uc, mycpu)->env.tr.base = *(uint64_t *)value;
|
||||
break;
|
||||
case UC_X86_REG_TR_ATTR:
|
||||
WRITE_DWORD(X86_CPU(uc, mycpu)->env.tr.flags, *(uint32_t *)value);
|
||||
case UC_X86_REG_TR:
|
||||
X86_CPU(uc, mycpu)->env.tr.limit = ((SegmentCache *)value)->limit;
|
||||
X86_CPU(uc, mycpu)->env.tr.base = ((SegmentCache *)value)->base;
|
||||
X86_CPU(uc, mycpu)->env.tr.selector = (uint16_t)((SegmentCache *)value)->selector;
|
||||
X86_CPU(uc, mycpu)->env.tr.flags = ((SegmentCache *)value)->flags;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
|
|
@ -47,16 +47,30 @@ static void test_idt_gdt_i386(/*void **state*/)
|
|||
uc_engine *uc;
|
||||
uc_err err;
|
||||
uint8_t buf[6];
|
||||
x86_mmr idt;
|
||||
x86_mmr gdt;
|
||||
x86_mmr ldt;
|
||||
x86_mmr tr;
|
||||
|
||||
const uint8_t code[] = "\x0f\x01\x0c\x24\x0f\x01\x44\x24\x06"; // sidt [esp]; sgdt [esp+6]
|
||||
const uint64_t address = 0x1000000;
|
||||
|
||||
int r_esp = address + 0x1000 - 0x100; // initial esp
|
||||
|
||||
int idt_base = 0x12345678;
|
||||
int idt_limit = 0xabcd;
|
||||
int gdt_base = 0x87654321;
|
||||
int gdt_limit = 0xdcba;
|
||||
idt.base = 0x12345678;
|
||||
idt.limit = 0xabcd;
|
||||
gdt.base = 0x87654321;
|
||||
gdt.limit = 0xdcba;
|
||||
|
||||
ldt.base = 0xfedcba98;
|
||||
ldt.limit = 0x11111111;
|
||||
ldt.selector = 0x3333;
|
||||
ldt.flags = 0x55555555;
|
||||
|
||||
tr.base = 0x22222222;
|
||||
tr.limit = 0x33333333;
|
||||
tr.selector = 0x4444;
|
||||
tr.flags = 0x66666666;
|
||||
|
||||
// Initialize emulator in X86-32bit mode
|
||||
err = uc_open(UC_ARCH_X86, UC_MODE_32, &uc);
|
||||
|
@ -73,36 +87,44 @@ static void test_idt_gdt_i386(/*void **state*/)
|
|||
// initialize machine registers
|
||||
err = uc_reg_write(uc, UC_X86_REG_ESP, &r_esp);
|
||||
uc_assert_success(err);
|
||||
err = uc_reg_write(uc, UC_X86_REG_IDTR_BASE, &idt_base);
|
||||
err = uc_reg_write(uc, UC_X86_REG_IDTR, &idt);
|
||||
uc_assert_success(err);
|
||||
err = uc_reg_write(uc, UC_X86_REG_IDTR_LIMIT, &idt_limit);
|
||||
uc_assert_success(err);
|
||||
err = uc_reg_write(uc, UC_X86_REG_GDTR_BASE, &gdt_base);
|
||||
uc_assert_success(err);
|
||||
err = uc_reg_write(uc, UC_X86_REG_GDTR_LIMIT, &gdt_limit);
|
||||
err = uc_reg_write(uc, UC_X86_REG_GDTR, &gdt);
|
||||
uc_assert_success(err);
|
||||
|
||||
idt_base = 0;
|
||||
idt_limit = 0;
|
||||
gdt_base = 0;
|
||||
gdt_limit = 0;
|
||||
idt.base = 0;
|
||||
idt.limit = 0;
|
||||
gdt.base = 0;
|
||||
gdt.limit = 0;
|
||||
|
||||
// emulate machine code in infinite time
|
||||
err = uc_emu_start(uc, address, address+sizeof(code)-1, 0, 0);
|
||||
uc_assert_success(err);
|
||||
|
||||
|
||||
uc_reg_read(uc, UC_X86_REG_IDTR_BASE, &idt_base);
|
||||
assert(idt_base == 0x12345678);
|
||||
uc_reg_read(uc, UC_X86_REG_IDTR, &idt);
|
||||
assert(idt.base == 0x12345678);
|
||||
assert(idt.limit == 0xabcd);
|
||||
|
||||
uc_reg_read(uc, UC_X86_REG_IDTR_LIMIT, &idt_limit);
|
||||
assert(idt_limit == 0xabcd);
|
||||
uc_reg_read(uc, UC_X86_REG_GDTR, &gdt);
|
||||
assert(gdt.base == 0x87654321);
|
||||
assert(gdt.limit == 0xdcba);
|
||||
|
||||
uc_reg_read(uc, UC_X86_REG_GDTR_BASE, &gdt_base);
|
||||
assert(gdt_base == 0x87654321);
|
||||
//userspace can only set ldt selector, remainder are loaded from
|
||||
//GDT/LDT, but we allow all to emulator user
|
||||
uc_reg_read(uc, UC_X86_REG_LDTR, &ldt);
|
||||
assert(ldt.base == 0xfedcba98);
|
||||
assert(ldt.limit == 0x11111111);
|
||||
assert(ldt.selector == 0x3333);
|
||||
assert(ldt.flags = 0x55555555);
|
||||
|
||||
uc_reg_read(uc, UC_X86_REG_GDTR_LIMIT, &gdt_limit);
|
||||
assert(gdt_limit == 0xdcba);
|
||||
//userspace can only set tr selector, remainder are loaded from
|
||||
//GDT/LDT, but we allow all to emulator user
|
||||
uc_reg_read(uc, UC_X86_REG_TR, &tr);
|
||||
assert(tr.base == 0x22222222);
|
||||
assert(tr.limit == 0x33333333);
|
||||
assert(tr.selector == 0x4444);
|
||||
assert(tr.flags = 0x66666666);
|
||||
|
||||
// read from memory
|
||||
err = uc_mem_read(uc, r_esp, buf, 6);
|
||||
|
|
Loading…
Reference in New Issue