mirror of
https://github.com/GaloisInc/macaw.git
synced 2024-12-29 00:59:09 +03:00
Bump submodules
This commit is contained in:
parent
230b318dcf
commit
64d71737af
@ -2,8 +2,8 @@
|
|||||||
Copyright : (c) Galois, Inc 2017
|
Copyright : (c) Galois, Inc 2017
|
||||||
Maintainer : Joe Hendrix <jhendrix@galois.com>
|
Maintainer : Joe Hendrix <jhendrix@galois.com>
|
||||||
|
|
||||||
This exports the "simplifiable CFG" interface. A CFG designed for
|
This exports the "simplifiable CFG" interface. This CFG is designed
|
||||||
optimization.
|
for simple optimizations including mem2reg
|
||||||
-}
|
-}
|
||||||
{-# LANGUAGE DataKinds #-}
|
{-# LANGUAGE DataKinds #-}
|
||||||
{-# LANGUAGE GADTs #-}
|
{-# LANGUAGE GADTs #-}
|
||||||
@ -22,6 +22,459 @@ module Data.Macaw.SCFG
|
|||||||
, module Data.Macaw.CFG.App
|
, module Data.Macaw.CFG.App
|
||||||
) where
|
) where
|
||||||
|
|
||||||
|
{-
|
||||||
|
Concepts:
|
||||||
|
|
||||||
|
initial(r): This denotes the value in r when the function is called.
|
||||||
|
|
||||||
|
|
||||||
|
How will the algorithm work?
|
||||||
|
|
||||||
|
Example, suppose we have the following program:
|
||||||
|
|
||||||
|
simple.c:
|
||||||
|
int add(int x, int y) {
|
||||||
|
return x+y;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
gcc -c -o simple.o simple.c
|
||||||
|
ubuntu64:~% gobjdump -d simple.c
|
||||||
|
|
||||||
|
|
||||||
|
Then we run the following to get the disassembly:
|
||||||
|
|
||||||
|
> gcc -c -o simple.o simple.c && objdump -d simple.o
|
||||||
|
|
||||||
|
0000000000000000 <add>:
|
||||||
|
0: 55 push %rbp
|
||||||
|
1: 48 89 e5 mov %rsp,%rbp
|
||||||
|
4: 89 7d fc mov %edi,-0x4(%rbp)
|
||||||
|
7: 89 75 f8 mov %esi,-0x8(%rbp)
|
||||||
|
a: 8b 55 fc mov -0x4(%rbp),%edx
|
||||||
|
d: 8b 45 f8 mov -0x8(%rbp),%eax
|
||||||
|
10: 01 d0 add %edx,%eax
|
||||||
|
12: 5d pop %rbp
|
||||||
|
13: c3 retq
|
||||||
|
|
||||||
|
// Reopt outputs the following:
|
||||||
|
|
||||||
|
function add @ segment1+0x0
|
||||||
|
segment1+0x0:
|
||||||
|
# segment1+0x0: push rbp
|
||||||
|
r23 := (bv_add rsp_0 (0xfffffffffffffff8 :: [64]))
|
||||||
|
write_mem r23 rbp_0
|
||||||
|
# segment1+0x1: mov rbp,rsp
|
||||||
|
# segment1+0x4: mov DWORD PTR [rbp-0x4],edi
|
||||||
|
r24 := (trunc rdi_0 32)
|
||||||
|
r25 := (bv_add rsp_0 (0xfffffffffffffff4 :: [64]))
|
||||||
|
write_mem r25 r24
|
||||||
|
# segment1+0x7: mov DWORD PTR [rbp-0x8],esi
|
||||||
|
r26 := (trunc rsi_0 32)
|
||||||
|
r27 := (bv_add rsp_0 (0xfffffffffffffff0 :: [64]))
|
||||||
|
write_mem r27 r26
|
||||||
|
# segment1+0xa: mov edx,DWORD PTR [rbp-0x4]
|
||||||
|
r28 := read_mem r25 (bvle 4)
|
||||||
|
r29 := (uext r28 64)
|
||||||
|
# segment1+0xd: mov eax,DWORD PTR [rbp-0x8]
|
||||||
|
r30 := read_mem r27 (bvle 4)
|
||||||
|
# segment1+0x10: add eax,edx
|
||||||
|
r32 := (sadc_overflows r30 r28 false)
|
||||||
|
r33 := (trunc r30 4)
|
||||||
|
r34 := (trunc r28 4)
|
||||||
|
r35 := (uadc_overflows r33 r34 false)
|
||||||
|
r36 := (uadc_overflows r30 r28 false)
|
||||||
|
r37 := (bv_add r30 r28)
|
||||||
|
r38 := (bv_slt r37 (0x0 :: [32]))
|
||||||
|
r39 := (eq r37 (0x0 :: [32]))
|
||||||
|
r40 := (trunc r37 8)
|
||||||
|
r41 := (even_parity r40)
|
||||||
|
r42 := (uext r37 64)
|
||||||
|
# segment1+0x12: pop rbp
|
||||||
|
r43 := read_mem r23 (bvle 8)
|
||||||
|
# segment1+0x13: ret
|
||||||
|
r44 := read_mem rsp_0 (bvle 8)
|
||||||
|
r45 := (bv_add rsp_0 (0x8 :: [64]))
|
||||||
|
return
|
||||||
|
{ rip = r44
|
||||||
|
, rax = r42
|
||||||
|
, rdx = r29
|
||||||
|
, rsp = r45
|
||||||
|
, rbp = r43
|
||||||
|
, cf = r36
|
||||||
|
, pf = r41
|
||||||
|
, af = r35
|
||||||
|
, zf = r39
|
||||||
|
, sf = r38
|
||||||
|
, of = r32
|
||||||
|
}
|
||||||
|
|
||||||
|
We would like the following:
|
||||||
|
|
||||||
|
````
|
||||||
|
function add @ segment1+0x0
|
||||||
|
|
||||||
|
Calling convention:
|
||||||
|
stack grows down
|
||||||
|
top of stack: rsp;
|
||||||
|
|
||||||
|
dont care about writes in range [rsp-16..rsp].
|
||||||
|
|
||||||
|
return_addr: *rsp;
|
||||||
|
final(ip) = read(initial_memory, rsp);
|
||||||
|
final(rsp) = initial(rsp)+8;
|
||||||
|
final(rbp) = initial(rbp);
|
||||||
|
|
||||||
|
segment1+0x0:
|
||||||
|
r28 := (trunc rdi_0 32)
|
||||||
|
r30 := (trunc rsi_0 32)
|
||||||
|
r37 := (bv_add r30 r28)
|
||||||
|
return
|
||||||
|
{ rax = (uext r37 64)
|
||||||
|
, rdx = (uext r28 64)
|
||||||
|
, af = (uadc_overflows (trunc r30 4) (trunc r28 4) false)
|
||||||
|
, cf = (uadc_overflows r30 r28 false)
|
||||||
|
, of = (sadc_overflows r30 r28 false)
|
||||||
|
, pf = (even_parity (trunc r37 8))
|
||||||
|
, zf = (eq r37 (0x0 :: [32]))
|
||||||
|
, sf = (bv_slt r37 (0x0 :: [32]))
|
||||||
|
}
|
||||||
|
|
||||||
|
Mask example
|
||||||
|
======
|
||||||
|
|
||||||
|
|
||||||
|
long mask(long c, long* a, long v) {
|
||||||
|
long r = 0;
|
||||||
|
for (long i = 0; i != c; ++c) {
|
||||||
|
if (a[i] == v) {
|
||||||
|
r = 2*r + 1;
|
||||||
|
} else {
|
||||||
|
r = 2*r;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
> gcc -c -o mask.o mask.c && objdump -d mask.o
|
||||||
|
|
||||||
|
|
||||||
|
0000000000000014 <mask>:
|
||||||
|
14: 55 push %rbp
|
||||||
|
15: 48 89 e5 mov %rsp,%rbp
|
||||||
|
18: 48 89 7d e8 mov %rdi,-0x18(%rbp)
|
||||||
|
1c: 48 89 75 e0 mov %rsi,-0x20(%rbp)
|
||||||
|
20: 48 89 55 d8 mov %rdx,-0x28(%rbp)
|
||||||
|
24: 48 c7 45 f0 00 00 00 movq $0x0,-0x10(%rbp)
|
||||||
|
2b: 00
|
||||||
|
2c: 48 c7 45 f8 00 00 00 movq $0x0,-0x8(%rbp)
|
||||||
|
33: 00
|
||||||
|
34: eb 36 jmp 6c <mask+0x58>
|
||||||
|
36: 48 8b 45 f8 mov -0x8(%rbp),%rax
|
||||||
|
3a: 48 8d 14 c5 00 00 00 lea 0x0(,%rax,8),%rdx
|
||||||
|
41: 00
|
||||||
|
42: 48 8b 45 e0 mov -0x20(%rbp),%rax
|
||||||
|
46: 48 01 d0 add %rdx,%rax
|
||||||
|
49: 48 8b 00 mov (%rax),%rax
|
||||||
|
4c: 48 3b 45 d8 cmp -0x28(%rbp),%rax
|
||||||
|
50: 75 11 jne 63 <mask+0x4f>
|
||||||
|
52: 48 8b 45 f0 mov -0x10(%rbp),%rax
|
||||||
|
56: 48 01 c0 add %rax,%rax
|
||||||
|
59: 48 83 c0 01 add $0x1,%rax
|
||||||
|
5d: 48 89 45 f0 mov %rax,-0x10(%rbp)
|
||||||
|
61: eb 04 jmp 67 <mask+0x53>
|
||||||
|
63: 48 d1 65 f0 shlq -0x10(%rbp)
|
||||||
|
67: 48 83 45 e8 01 addq $0x1,-0x18(%rbp)
|
||||||
|
6c: 48 8b 45 f8 mov -0x8(%rbp),%rax
|
||||||
|
70: 48 3b 45 e8 cmp -0x18(%rbp),%rax
|
||||||
|
74: 75 c0 jne 36 <mask+0x22>
|
||||||
|
76: 48 8b 45 f0 mov -0x10(%rbp),%rax
|
||||||
|
7a: 5d pop %rbp
|
||||||
|
7b: c3 retq
|
||||||
|
|
||||||
|
|
||||||
|
function mask @ segment1+0x14
|
||||||
|
segment1+0x14:
|
||||||
|
# Predecessors = []
|
||||||
|
r6 := (bv_add rsp_0 (0xfffffffffffffff8 :: [64]))
|
||||||
|
r7 := (bv_add rsp_0 (0xffffffffffffffe0 :: [64]))
|
||||||
|
r8 := (bv_add rsp_0 (0xffffffffffffffd8 :: [64]))
|
||||||
|
r9 := (bv_add rsp_0 (0xffffffffffffffd0 :: [64]))
|
||||||
|
r10 := (bv_add rsp_0 (0xffffffffffffffe8 :: [64]))
|
||||||
|
r11 := (bv_add rsp_0 (0xfffffffffffffff0 :: [64]))
|
||||||
|
write_mem r6 rbp_0
|
||||||
|
write_mem r7 rdi_0
|
||||||
|
write_mem r8 rsi_0
|
||||||
|
write_mem r9 rdx_0
|
||||||
|
write_mem r10 0x0 :: [64]
|
||||||
|
write_mem r11 0x0 :: [64]
|
||||||
|
jump segment1+0x6c
|
||||||
|
{ rip = segment1+0x6c
|
||||||
|
, rsp = r6
|
||||||
|
, rbp = r6
|
||||||
|
, df = false
|
||||||
|
, x87top = 0x7 :: [3]
|
||||||
|
}
|
||||||
|
segment1+0x36:
|
||||||
|
# Dominators: 0x6c
|
||||||
|
|
||||||
|
#Predecessors = [0x6c]
|
||||||
|
rbp_0 = phi [(0x14, rbp_0)]
|
||||||
|
rsp_0 = phi [(0x14, rsp_0)]
|
||||||
|
|
||||||
|
r662 := read_mem (rbp_0 - 8) (bvle 8)
|
||||||
|
r665 := read_mem (rbp_0 - 0x20) (bvle 8)
|
||||||
|
r678 := read_mem (rbp_0 - 0x28) (bvle 8)
|
||||||
|
|
||||||
|
r663 := (bv_mul (0x8 :: [64]) r662)
|
||||||
|
r671 := (bv_add r665 r663)
|
||||||
|
r676 := read_mem r671 (bvle 8)
|
||||||
|
r679 := (ssbb_overflows r676 r678 false)
|
||||||
|
r680 := (trunc r676 4)
|
||||||
|
r681 := (trunc r678 4)
|
||||||
|
r682 := (bv_ult r680 r681)
|
||||||
|
r683 := (bv_ult r676 r678)
|
||||||
|
r684 := (bv_sub r676 r678)
|
||||||
|
r685 := (bv_slt r684 (0x0 :: [64]))
|
||||||
|
r686 := (eq r676 r678)
|
||||||
|
r687 := (trunc r684 8)
|
||||||
|
r688 := (even_parity r687)
|
||||||
|
# segment1+0x50: jne 50+11
|
||||||
|
r689 := (mux r686 (segment1+0x52) (segment1+0x63))
|
||||||
|
ite r686
|
||||||
|
{
|
||||||
|
|
||||||
|
jump segment1+0x52
|
||||||
|
{ rip = segment1+0x52
|
||||||
|
, rax = r676
|
||||||
|
, rdx = r663
|
||||||
|
, cf = r683
|
||||||
|
, pf = r688
|
||||||
|
, af = r682
|
||||||
|
, zf = r686
|
||||||
|
, sf = r685
|
||||||
|
, df = false
|
||||||
|
, of = r679
|
||||||
|
, x87top = 0x7 :: [3]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
|
||||||
|
jump segment1+0x63
|
||||||
|
{ rip = segment1+0x63
|
||||||
|
, rax = r676
|
||||||
|
, rdx = r663
|
||||||
|
, cf = r683
|
||||||
|
, pf = r688
|
||||||
|
, af = r682
|
||||||
|
, zf = r686
|
||||||
|
, sf = r685
|
||||||
|
, df = false
|
||||||
|
, of = r679
|
||||||
|
, x87top = 0x7 :: [3]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
segment1+0x52:
|
||||||
|
# Predecessors 0x36
|
||||||
|
rbp_0 = phi [(0x36, rbp_0)]
|
||||||
|
rsp_0 = phi [(0x36, rsp_0)]
|
||||||
|
|
||||||
|
# segment1+0x52: mov rax,QWORD PTR [rbp-0x10]
|
||||||
|
r711 := read_mem (rbp_0 - 16) (bvle 8)
|
||||||
|
# segment1+0x56: add rax,rax
|
||||||
|
r716 := (bv_add r711 r711)
|
||||||
|
# segment1+0x59: add rax,0x1
|
||||||
|
r721 := (sadc_overflows r716 (0x1 :: [64]) false)
|
||||||
|
r722 := (trunc r716 4)
|
||||||
|
r723 := (uadc_overflows r722 (0x1 :: [4]) false)
|
||||||
|
r724 := (uadc_overflows r716 (0x1 :: [64]) false)
|
||||||
|
r725 := (bv_add r716 (0x1 :: [64]))
|
||||||
|
r726 := (bv_slt r725 (0x0 :: [64]))
|
||||||
|
r727 := (eq r716 (0xffffffffffffffff :: [64]))
|
||||||
|
r728 := (trunc r725 8)
|
||||||
|
r729 := (even_parity r728)
|
||||||
|
# segment1+0x5d: mov QWORD PTR [rbp-0x10],rax
|
||||||
|
write_mem (rbp_0 - 0x10) r725
|
||||||
|
# segment1+0x61: jmp 61+4
|
||||||
|
jump segment1+0x67
|
||||||
|
{ rip = segment1+0x67
|
||||||
|
, rax = r725
|
||||||
|
, cf = r724
|
||||||
|
, pf = r729
|
||||||
|
, af = r723
|
||||||
|
, zf = r727
|
||||||
|
, sf = r726
|
||||||
|
, df = false
|
||||||
|
, of = r721
|
||||||
|
, x87top = 0x7 :: [3]
|
||||||
|
}
|
||||||
|
segment1+0x63:
|
||||||
|
# Predecessors 0x36
|
||||||
|
rbp_0 = phi [(0x14, rbp_0)]
|
||||||
|
rsp_0 = phi [(0x14, rsp_0)]
|
||||||
|
|
||||||
|
# segment1+0x63: shl QWORD PTR [rbp-0x10],1
|
||||||
|
r743 := read_mem (rbp_0 - 0x10) (bvle 8)
|
||||||
|
r744 := undef :: [bool]
|
||||||
|
r746 := (bv_slt r743 (0x0 :: [64]))
|
||||||
|
r747 := (bv_slt r743 (0x0 :: [64]))
|
||||||
|
r748 := (bv_shl r743 (0x1 :: [64]))
|
||||||
|
r749 := (bv_slt r748 (0x0 :: [64]))
|
||||||
|
r750 := (eq r748 (0x0 :: [64]))
|
||||||
|
r751 := (trunc r748 8)
|
||||||
|
r752 := (even_parity r751)
|
||||||
|
write_mem (rbp_0 - 0x10) r748
|
||||||
|
jump segment1+0x67
|
||||||
|
{ rip = segment1+0x67
|
||||||
|
, cf = r747
|
||||||
|
, pf = r752
|
||||||
|
, af = r744
|
||||||
|
, zf = r750
|
||||||
|
, sf = r749
|
||||||
|
, df = false
|
||||||
|
, of = r746
|
||||||
|
, x87top = 0x7 :: [3]
|
||||||
|
}
|
||||||
|
segment1+0x67:
|
||||||
|
# Predecessors 0x52, 0x63
|
||||||
|
rbp_0 = phi [(0x14, rbp_0)]
|
||||||
|
rsp_0 = phi [(0x14, rsp_0)]
|
||||||
|
|
||||||
|
# segment1+0x67: add QWORD PTR [rbp-0x18],0x1
|
||||||
|
r591 := (bv_add rbp_0 (0xffffffffffffffe8 :: [64]))
|
||||||
|
r592 := read_mem r591 (bvle 8)
|
||||||
|
r593 := (sadc_overflows r592 (0x1 :: [64]) false)
|
||||||
|
r594 := (trunc r592 4)
|
||||||
|
r595 := (uadc_overflows r594 (0x1 :: [4]) false)
|
||||||
|
r596 := (uadc_overflows r592 (0x1 :: [64]) false)
|
||||||
|
r597 := (bv_add r592 (0x1 :: [64]))
|
||||||
|
r598 := (bv_slt r597 (0x0 :: [64]))
|
||||||
|
r599 := (eq r592 (0xffffffffffffffff :: [64]))
|
||||||
|
r600 := (trunc r597 8)
|
||||||
|
r601 := (even_parity r600)
|
||||||
|
write_mem r591 r597
|
||||||
|
jump segment1+0x6c
|
||||||
|
{ rip = segment1+0x6c
|
||||||
|
, cf = r596
|
||||||
|
, pf = r601
|
||||||
|
, af = r595
|
||||||
|
, zf = r599
|
||||||
|
, sf = r598
|
||||||
|
, df = false
|
||||||
|
, of = r593
|
||||||
|
, x87top = 0x7 :: [3]
|
||||||
|
}
|
||||||
|
segment1+0x6c:
|
||||||
|
# Predecessors 0x14 0x67
|
||||||
|
rbp_0 = phi [(0x14, (rsp_0 - 8))]
|
||||||
|
rsp_0 = phi [(0x14, (rsp_0 - 8))]
|
||||||
|
|
||||||
|
|
||||||
|
r618 := read_mem (rbp_0 - 8) (bvle 8)
|
||||||
|
r620 := read_mem (rbp_0 - 0x18) (bvle 8)
|
||||||
|
r621 := (ssbb_overflows r618 r620 false)
|
||||||
|
r622 := (trunc r618 4)
|
||||||
|
r623 := (trunc r620 4)
|
||||||
|
r624 := (bv_ult r622 r623)
|
||||||
|
r625 := (bv_ult r618 r620)
|
||||||
|
r626 := (bv_sub r618 r620)
|
||||||
|
r627 := (bv_slt r626 (0x0 :: [64]))
|
||||||
|
r628 := (eq r618 r620)
|
||||||
|
r629 := (trunc r626 8)
|
||||||
|
r630 := (even_parity r629)
|
||||||
|
# segment1+0x74: jne 74+-40
|
||||||
|
r631 := (mux r628 (segment1+0x76) (segment1+0x36))
|
||||||
|
ite r628
|
||||||
|
{
|
||||||
|
|
||||||
|
jump segment1+0x76
|
||||||
|
{ rip = segment1+0x76
|
||||||
|
, rax = r618
|
||||||
|
, cf = r625
|
||||||
|
, pf = r630
|
||||||
|
, af = r624
|
||||||
|
, zf = r628
|
||||||
|
, sf = r627
|
||||||
|
, df = false
|
||||||
|
, of = r621
|
||||||
|
, x87top = 0x7 :: [3]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
|
||||||
|
jump segment1+0x36
|
||||||
|
{ rip = segment1+0x36
|
||||||
|
, rax = r618
|
||||||
|
, cf = r625
|
||||||
|
, pf = r630
|
||||||
|
, af = r624
|
||||||
|
, zf = r628
|
||||||
|
, sf = r627
|
||||||
|
, df = false
|
||||||
|
, of = r621
|
||||||
|
, x87top = 0x7 :: [3]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
segment1+0x76:
|
||||||
|
# Predecessors 0x6c
|
||||||
|
rbp_0 = phi [(0x14, (initial(rsp) - 8)]
|
||||||
|
rsp_0 = phi [(0x14, (initial(rsp) - 8)]
|
||||||
|
# Detect *(0x14, rsp_0) = return_addr
|
||||||
|
|
||||||
|
|
||||||
|
r761 := read_mem (initial(rsp) - 0x18) (bvle 8)
|
||||||
|
r762 := read_mem rsp_0 (bvle 8)
|
||||||
|
r764 := read_mem (rsp_0 + 8) (bvle 8)
|
||||||
|
return
|
||||||
|
{ rip = r764
|
||||||
|
, rax = r761
|
||||||
|
, rsp = initial(rsp) + 0x8
|
||||||
|
, rbp = r762
|
||||||
|
, df = false
|
||||||
|
, x87top = 0x7 :: [3]
|
||||||
|
}
|
||||||
|
|
||||||
|
0x14 -> 0x6c
|
||||||
|
0x6c -> 0x76 | 0x36
|
||||||
|
0x76 -> return
|
||||||
|
0x36 -> 0x52 | 0x63
|
||||||
|
0x52 -> 0x67
|
||||||
|
0x63 -> 0x67
|
||||||
|
0x67 -> 0x6c
|
||||||
|
|
||||||
|
|
||||||
|
0x14 -> 0x6c -> (0x36 -> (0x52|0x63|0x67))
|
||||||
|
-> 0x76
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
0x6c -> 0x36
|
||||||
|
0x36 -> 0x52 | 0x63
|
||||||
|
|
||||||
|
========================================================================
|
||||||
|
== Implementation
|
||||||
|
|
||||||
|
|
||||||
|
First goal:
|
||||||
|
- Generate a ordering over the functions:
|
||||||
|
|
||||||
|
- f <= g if call @g appears inside a block of f.
|
||||||
|
- f <= g if there exists an h such that call g(f, ...)
|
||||||
|
|
||||||
|
|
||||||
|
Stack:
|
||||||
|
|
||||||
|
Partial map from index to (BlockId,StmtIndex,Value) tuples.
|
||||||
|
|
||||||
|
Concepts
|
||||||
|
|
||||||
|
|
||||||
|
Build a dominator tree
|
||||||
|
|
||||||
|
-}
|
||||||
|
|
||||||
|
|
||||||
import Data.Macaw.CFG.AssignRhs
|
import Data.Macaw.CFG.AssignRhs
|
||||||
import Data.Macaw.CFG.App
|
import Data.Macaw.CFG.App
|
||||||
import Data.Macaw.Memory (AddrWidthRepr(..))
|
import Data.Macaw.Memory (AddrWidthRepr(..))
|
||||||
|
2
deps/flexdis86
vendored
2
deps/flexdis86
vendored
@ -1 +1 @@
|
|||||||
Subproject commit 9304269acef1c82e9ae3676649574e371ac54c36
|
Subproject commit 71c367e2269f84dcdfda21612288109312ba3fc6
|
Loading…
Reference in New Issue
Block a user