Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
go
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Kirill Smelkov
go
Commits
37499ebc
Commit
37499ebc
authored
Dec 08, 2010
by
Russ Cox
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
debug/elf, debug/macho: add ImportedLibraries, ImportedSymbols
R=r, iant CC=golang-dev
https://golang.org/cl/3470044
parent
f16c280f
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
384 additions
and
23 deletions
+384
-23
src/pkg/debug/elf/elf.go
src/pkg/debug/elf/elf.go
+6
-3
src/pkg/debug/elf/file.go
src/pkg/debug/elf/file.go
+143
-18
src/pkg/debug/macho/file.go
src/pkg/debug/macho/file.go
+157
-0
src/pkg/debug/macho/macho.go
src/pkg/debug/macho/macho.go
+78
-2
No files found.
src/pkg/debug/elf/elf.go
View file @
37499ebc
...
...
@@ -1356,9 +1356,12 @@ type Sym32 struct {
const
Sym32Size
=
16
func
ST_BIND
(
info
uint8
)
SymBind
{
return
SymBind
(
info
>>
4
)
}
func
ST_TYPE
(
bind
SymBind
,
typ
SymType
)
uint8
{
return
uint8
(
bind
)
<<
4
|
uint8
(
typ
)
&
0xf
}
func
ST_VISIBILITY
(
other
uint8
)
SymVis
{
return
SymVis
(
other
&
3
)
}
func
ST_BIND
(
info
uint8
)
SymBind
{
return
SymBind
(
info
>>
4
)
}
func
ST_TYPE
(
info
uint8
)
SymType
{
return
SymType
(
info
&
0xF
)
}
func
ST_INFO
(
bind
SymBind
,
typ
SymType
)
uint8
{
return
uint8
(
bind
)
<<
4
|
uint8
(
typ
)
&
0xf
}
func
ST_VISIBILITY
(
other
uint8
)
SymVis
{
return
SymVis
(
other
&
3
)
}
/*
* ELF64
...
...
src/pkg/debug/elf/file.go
View file @
37499ebc
...
...
@@ -75,6 +75,15 @@ func (s *Section) Data() ([]byte, os.Error) {
return
dat
[
0
:
n
],
err
}
// stringTable reads and returns the string table given by the
// specified link value.
func
(
f
*
File
)
stringTable
(
link
uint32
)
([]
byte
,
os
.
Error
)
{
if
link
<=
0
||
link
>=
uint32
(
len
(
f
.
Sections
))
{
return
nil
,
os
.
ErrorString
(
"section has invalid string table link"
)
}
return
f
.
Sections
[
link
]
.
Data
()
}
// Open returns a new ReadSeeker reading the ELF section.
func
(
s
*
Section
)
Open
()
io
.
ReadSeeker
{
return
io
.
NewSectionReader
(
s
.
sr
,
0
,
1
<<
63
-
1
)
}
...
...
@@ -108,9 +117,9 @@ func (p *Prog) Open() io.ReadSeeker { return io.NewSectionReader(p.sr, 0, 1<<63-
// A Symbol represents an entry in an ELF symbol table section.
type
Symbol
struct
{
Name
uint32
Name
string
Info
,
Other
byte
Section
uint32
Section
SectionIndex
Value
,
Size
uint64
}
...
...
@@ -160,6 +169,17 @@ func (f *File) Close() os.Error {
return
err
}
// SectionByType returns the first section in f with the
// given type, or nil if there is no such section.
func
(
f
*
File
)
SectionByType
(
typ
SectionType
)
*
Section
{
for
_
,
s
:=
range
f
.
Sections
{
if
s
.
Type
==
typ
{
return
s
}
}
return
nil
}
// NewFile creates a new File for accessing an ELF binary in an underlying reader.
// The ELF binary is expected to start at position 0 in the ReaderAt.
func
NewFile
(
r
io
.
ReaderAt
)
(
*
File
,
os
.
Error
)
{
...
...
@@ -293,9 +313,8 @@ func NewFile(r io.ReaderAt) (*File, os.Error) {
}
// Load section header string table.
s
:=
f
.
Sections
[
shstrndx
]
shstrtab
:=
make
([]
byte
,
s
.
Size
)
if
_
,
err
:=
r
.
ReadAt
(
shstrtab
,
int64
(
s
.
Offset
));
err
!=
nil
{
shstrtab
,
err
:=
f
.
Sections
[
shstrndx
]
.
Data
()
if
err
!=
nil
{
return
nil
,
err
}
for
i
,
s
:=
range
f
.
Sections
{
...
...
@@ -309,25 +328,65 @@ func NewFile(r io.ReaderAt) (*File, os.Error) {
return
f
,
nil
}
func
(
f
*
File
)
getSymbols
()
([]
Symbol
,
os
.
Error
)
{
// getSymbols returns a slice of Symbols from parsing the symbol table
// with the given type.
func
(
f
*
File
)
getSymbols
(
typ
SectionType
)
([]
Symbol
,
os
.
Error
)
{
switch
f
.
Class
{
case
ELFCLASS64
:
return
f
.
getSymbols64
()
return
f
.
getSymbols64
(
typ
)
case
ELFCLASS32
:
return
f
.
getSymbols32
(
typ
)
}
return
nil
,
os
.
ErrorString
(
"not implemented"
)
}
// GetSymbols returns a slice of Symbols from parsing the symbol table.
func
(
f
*
File
)
getSymbols64
()
([]
Symbol
,
os
.
Error
)
{
var
symtabSection
*
Section
for
_
,
section
:=
range
f
.
Sections
{
if
section
.
Type
==
SHT_SYMTAB
{
symtabSection
=
section
break
}
func
(
f
*
File
)
getSymbols32
(
typ
SectionType
)
([]
Symbol
,
os
.
Error
)
{
symtabSection
:=
f
.
SectionByType
(
typ
)
if
symtabSection
==
nil
{
return
nil
,
os
.
ErrorString
(
"no symbol section"
)
}
data
,
err
:=
symtabSection
.
Data
()
if
err
!=
nil
{
return
nil
,
os
.
ErrorString
(
"cannot load symbol section"
)
}
symtab
:=
bytes
.
NewBuffer
(
data
)
if
symtab
.
Len
()
%
Sym32Size
!=
0
{
return
nil
,
os
.
ErrorString
(
"length of symbol section is not a multiple of SymSize"
)
}
strdata
,
err
:=
f
.
stringTable
(
symtabSection
.
Link
)
if
err
!=
nil
{
return
nil
,
os
.
ErrorString
(
"cannot load string table section"
)
}
// The first entry is all zeros.
var
skip
[
Sym32Size
]
byte
symtab
.
Read
(
skip
[
0
:
])
symbols
:=
make
([]
Symbol
,
symtab
.
Len
()
/
Sym32Size
)
i
:=
0
var
sym
Sym32
for
symtab
.
Len
()
>
0
{
binary
.
Read
(
symtab
,
f
.
ByteOrder
,
&
sym
)
str
,
_
:=
getString
(
strdata
,
int
(
sym
.
Name
))
symbols
[
i
]
.
Name
=
str
symbols
[
i
]
.
Info
=
sym
.
Info
symbols
[
i
]
.
Other
=
sym
.
Other
symbols
[
i
]
.
Section
=
SectionIndex
(
sym
.
Shndx
)
symbols
[
i
]
.
Value
=
uint64
(
sym
.
Value
)
symbols
[
i
]
.
Size
=
uint64
(
sym
.
Size
)
i
++
}
return
symbols
,
nil
}
func
(
f
*
File
)
getSymbols64
(
typ
SectionType
)
([]
Symbol
,
os
.
Error
)
{
symtabSection
:=
f
.
SectionByType
(
typ
)
if
symtabSection
==
nil
{
return
nil
,
os
.
ErrorString
(
"no symbol section"
)
}
...
...
@@ -341,6 +400,11 @@ func (f *File) getSymbols64() ([]Symbol, os.Error) {
return
nil
,
os
.
ErrorString
(
"length of symbol section is not a multiple of Sym64Size"
)
}
strdata
,
err
:=
f
.
stringTable
(
symtabSection
.
Link
)
if
err
!=
nil
{
return
nil
,
os
.
ErrorString
(
"cannot load string table section"
)
}
// The first entry is all zeros.
var
skip
[
Sym64Size
]
byte
symtab
.
Read
(
skip
[
0
:
])
...
...
@@ -351,10 +415,11 @@ func (f *File) getSymbols64() ([]Symbol, os.Error) {
var
sym
Sym64
for
symtab
.
Len
()
>
0
{
binary
.
Read
(
symtab
,
f
.
ByteOrder
,
&
sym
)
symbols
[
i
]
.
Name
=
sym
.
Name
str
,
_
:=
getString
(
strdata
,
int
(
sym
.
Name
))
symbols
[
i
]
.
Name
=
str
symbols
[
i
]
.
Info
=
sym
.
Info
symbols
[
i
]
.
Other
=
sym
.
Other
symbols
[
i
]
.
Section
=
uint32
(
sym
.
Shndx
)
symbols
[
i
]
.
Section
=
SectionIndex
(
sym
.
Shndx
)
symbols
[
i
]
.
Value
=
sym
.
Value
symbols
[
i
]
.
Size
=
sym
.
Size
i
++
...
...
@@ -403,7 +468,7 @@ func (f *File) applyRelocationsAMD64(dst []byte, rels []byte) os.Error {
return
os
.
ErrorString
(
"length of relocation section is not a multiple of Sym64Size"
)
}
symbols
,
err
:=
f
.
getSymbols
()
symbols
,
err
:=
f
.
getSymbols
(
SHT_SYMTAB
)
if
err
!=
nil
{
return
err
}
...
...
@@ -478,3 +543,63 @@ func (f *File) DWARF() (*dwarf.Data, os.Error) {
abbrev
,
info
,
str
:=
dat
[
0
],
dat
[
1
],
dat
[
2
]
return
dwarf
.
New
(
abbrev
,
nil
,
nil
,
info
,
nil
,
nil
,
nil
,
str
)
}
// ImportedSymbols returns the names of all symbols
// referred to by the binary f that are expected to be
// satisfied by other libraries at dynamic load time.
// It does not return weak symbols.
func
(
f
*
File
)
ImportedSymbols
()
([]
string
,
os
.
Error
)
{
sym
,
err
:=
f
.
getSymbols
(
SHT_DYNSYM
)
if
err
!=
nil
{
return
nil
,
err
}
var
all
[]
string
for
_
,
s
:=
range
sym
{
if
ST_BIND
(
s
.
Info
)
==
STB_GLOBAL
&&
s
.
Section
==
SHN_UNDEF
{
all
=
append
(
all
,
s
.
Name
)
}
}
return
all
,
nil
}
// ImportedLibraries returns the names of all libraries
// referred to by the binary f that are expected to be
// linked with the binary at dynamic link time.
func
(
f
*
File
)
ImportedLibraries
()
([]
string
,
os
.
Error
)
{
ds
:=
f
.
SectionByType
(
SHT_DYNAMIC
)
if
ds
==
nil
{
// not dynamic, so no libraries
return
nil
,
nil
}
d
,
err
:=
ds
.
Data
()
if
err
!=
nil
{
return
nil
,
err
}
str
,
err
:=
f
.
stringTable
(
ds
.
Link
)
if
err
!=
nil
{
return
nil
,
err
}
var
all
[]
string
for
len
(
d
)
>
0
{
var
tag
DynTag
var
value
uint64
switch
f
.
Class
{
case
ELFCLASS32
:
tag
=
DynTag
(
f
.
ByteOrder
.
Uint32
(
d
[
0
:
4
]))
value
=
uint64
(
f
.
ByteOrder
.
Uint32
(
d
[
4
:
8
]))
d
=
d
[
8
:
]
case
ELFCLASS64
:
tag
=
DynTag
(
f
.
ByteOrder
.
Uint64
(
d
[
0
:
8
]))
value
=
f
.
ByteOrder
.
Uint64
(
d
[
8
:
16
])
d
=
d
[
16
:
]
}
if
tag
==
DT_NEEDED
{
s
,
ok
:=
getString
(
str
,
int
(
value
))
if
ok
{
all
=
append
(
all
,
s
)
}
}
}
return
all
,
nil
}
src/pkg/debug/macho/file.go
View file @
37499ebc
...
...
@@ -24,6 +24,9 @@ type File struct {
Loads
[]
Load
Sections
[]
*
Section
Symtab
*
Symtab
Dysymtab
*
Dysymtab
closer
io
.
Closer
}
...
...
@@ -112,6 +115,28 @@ func (s *Section) Data() ([]byte, os.Error) {
// Open returns a new ReadSeeker reading the Mach-O section.
func
(
s
*
Section
)
Open
()
io
.
ReadSeeker
{
return
io
.
NewSectionReader
(
s
.
sr
,
0
,
1
<<
63
-
1
)
}
// A Dylib represents a Mach-O load dynamic library command.
type
Dylib
struct
{
LoadBytes
Name
string
Time
uint32
CurrentVersion
uint32
CompatVersion
uint32
}
// A Symtab represents a Mach-O symbol table command.
type
Symtab
struct
{
LoadBytes
SymtabCmd
Syms
[]
Symbol
}
// A Dysymtab represents a Mach-O dynamic symbol table command.
type
Dysymtab
struct
{
LoadBytes
DysymtabCmd
IndirectSyms
[]
uint32
// indices into Symtab.Syms
}
/*
* Mach-O reader
...
...
@@ -217,6 +242,71 @@ func NewFile(r io.ReaderAt) (*File, os.Error) {
default
:
f
.
Loads
[
i
]
=
LoadBytes
(
cmddat
)
case
LoadCmdDylib
:
var
hdr
DylibCmd
b
:=
bytes
.
NewBuffer
(
cmddat
)
if
err
:=
binary
.
Read
(
b
,
bo
,
&
hdr
);
err
!=
nil
{
return
nil
,
err
}
l
:=
new
(
Dylib
)
if
hdr
.
Name
>=
uint32
(
len
(
cmddat
))
{
return
nil
,
&
FormatError
{
offset
,
"invalid name in dynamic library command"
,
hdr
.
Name
}
}
l
.
Name
=
cstring
(
cmddat
[
hdr
.
Name
:
])
l
.
Time
=
hdr
.
Time
l
.
CurrentVersion
=
hdr
.
CurrentVersion
l
.
CompatVersion
=
hdr
.
CompatVersion
l
.
LoadBytes
=
LoadBytes
(
cmddat
)
f
.
Loads
[
i
]
=
l
case
LoadCmdSymtab
:
var
hdr
SymtabCmd
b
:=
bytes
.
NewBuffer
(
cmddat
)
if
err
:=
binary
.
Read
(
b
,
bo
,
&
hdr
);
err
!=
nil
{
return
nil
,
err
}
strtab
:=
make
([]
byte
,
hdr
.
Strsize
)
if
_
,
err
:=
r
.
ReadAt
(
strtab
,
int64
(
hdr
.
Stroff
));
err
!=
nil
{
return
nil
,
err
}
var
symsz
int
if
f
.
Magic
==
Magic64
{
symsz
=
16
}
else
{
symsz
=
12
}
symdat
:=
make
([]
byte
,
int
(
hdr
.
Nsyms
)
*
symsz
)
if
_
,
err
:=
r
.
ReadAt
(
symdat
,
int64
(
hdr
.
Symoff
));
err
!=
nil
{
return
nil
,
err
}
st
,
err
:=
f
.
parseSymtab
(
symdat
,
strtab
,
cmddat
,
&
hdr
,
offset
)
if
err
!=
nil
{
return
nil
,
err
}
f
.
Loads
[
i
]
=
st
f
.
Symtab
=
st
case
LoadCmdDysymtab
:
var
hdr
DysymtabCmd
b
:=
bytes
.
NewBuffer
(
cmddat
)
if
err
:=
binary
.
Read
(
b
,
bo
,
&
hdr
);
err
!=
nil
{
return
nil
,
err
}
dat
:=
make
([]
byte
,
hdr
.
Nindirectsyms
*
4
)
if
_
,
err
:=
r
.
ReadAt
(
dat
,
int64
(
hdr
.
Indirectsymoff
));
err
!=
nil
{
return
nil
,
err
}
x
:=
make
([]
uint32
,
hdr
.
Nindirectsyms
)
if
err
:=
binary
.
Read
(
bytes
.
NewBuffer
(
dat
),
bo
,
x
);
err
!=
nil
{
return
nil
,
err
}
st
:=
new
(
Dysymtab
)
st
.
LoadBytes
=
LoadBytes
(
cmddat
)
st
.
DysymtabCmd
=
hdr
st
.
IndirectSyms
=
x
f
.
Loads
[
i
]
=
st
f
.
Dysymtab
=
st
case
LoadCmdSegment
:
var
seg32
Segment32
b
:=
bytes
.
NewBuffer
(
cmddat
)
...
...
@@ -301,6 +391,43 @@ func NewFile(r io.ReaderAt) (*File, os.Error) {
return
f
,
nil
}
func
(
f
*
File
)
parseSymtab
(
symdat
,
strtab
,
cmddat
[]
byte
,
hdr
*
SymtabCmd
,
offset
int64
)
(
*
Symtab
,
os
.
Error
)
{
bo
:=
f
.
ByteOrder
symtab
:=
make
([]
Symbol
,
hdr
.
Nsyms
)
b
:=
bytes
.
NewBuffer
(
symdat
)
for
i
:=
range
symtab
{
var
n
Nlist64
if
f
.
Magic
==
Magic64
{
if
err
:=
binary
.
Read
(
b
,
bo
,
&
n
);
err
!=
nil
{
return
nil
,
err
}
}
else
{
var
n32
Nlist32
if
err
:=
binary
.
Read
(
b
,
bo
,
&
n32
);
err
!=
nil
{
return
nil
,
err
}
n
.
Name
=
n32
.
Name
n
.
Type
=
n32
.
Type
n
.
Sect
=
n32
.
Sect
n
.
Desc
=
n32
.
Desc
n
.
Value
=
uint64
(
n32
.
Value
)
}
sym
:=
&
symtab
[
i
]
if
n
.
Name
>=
uint32
(
len
(
strtab
))
{
return
nil
,
&
FormatError
{
offset
,
"invalid name in symbol table"
,
n
.
Name
}
}
sym
.
Name
=
cstring
(
strtab
[
n
.
Name
:
])
sym
.
Type
=
n
.
Type
sym
.
Sect
=
n
.
Sect
sym
.
Desc
=
n
.
Desc
sym
.
Value
=
n
.
Value
}
st
:=
new
(
Symtab
)
st
.
LoadBytes
=
LoadBytes
(
cmddat
)
st
.
Syms
=
symtab
return
st
,
nil
}
func
(
f
*
File
)
pushSection
(
sh
*
Section
,
r
io
.
ReaderAt
)
{
f
.
Sections
=
append
(
f
.
Sections
,
sh
)
sh
.
sr
=
io
.
NewSectionReader
(
r
,
int64
(
sh
.
Offset
),
int64
(
sh
.
Size
))
...
...
@@ -358,3 +485,33 @@ func (f *File) DWARF() (*dwarf.Data, os.Error) {
abbrev
,
info
,
str
:=
dat
[
0
],
dat
[
1
],
dat
[
2
]
return
dwarf
.
New
(
abbrev
,
nil
,
nil
,
info
,
nil
,
nil
,
nil
,
str
)
}
// ImportedSymbols returns the names of all symbols
// referred to by the binary f that are expected to be
// satisfied by other libraries at dynamic load time.
func
(
f
*
File
)
ImportedSymbols
()
([]
string
,
os
.
Error
)
{
if
f
.
Dysymtab
==
nil
||
f
.
Symtab
==
nil
{
return
nil
,
&
FormatError
{
0
,
"missing symbol table"
,
nil
}
}
st
:=
f
.
Symtab
dt
:=
f
.
Dysymtab
var
all
[]
string
for
_
,
s
:=
range
st
.
Syms
[
dt
.
Iundefsym
:
dt
.
Iundefsym
+
dt
.
Nundefsym
]
{
all
=
append
(
all
,
s
.
Name
)
}
return
all
,
nil
}
// ImportedLibraries returns the paths of all libraries
// referred to by the binary f that are expected to be
// linked with the binary at dynamic link time.
func
(
f
*
File
)
ImportedLibraries
()
([]
string
,
os
.
Error
)
{
var
all
[]
string
for
_
,
l
:=
range
f
.
Loads
{
if
lib
,
ok
:=
l
.
(
*
Dylib
);
ok
{
all
=
append
(
all
,
lib
.
Name
)
}
}
return
all
,
nil
}
src/pkg/debug/macho/macho.go
View file @
37499ebc
...
...
@@ -59,16 +59,21 @@ type LoadCmd uint32
const
(
LoadCmdSegment
LoadCmd
=
1
LoadCmdS
egment64
LoadCmd
=
25
LoadCmdS
ymtab
LoadCmd
=
2
LoadCmdThread
LoadCmd
=
4
LoadCmdUnixThread
LoadCmd
=
5
// thread+stack
LoadCmdDysymtab
LoadCmd
=
11
LoadCmdDylib
LoadCmd
=
12
LoadCmdDylinker
LoadCmd
=
15
LoadCmdSegment64
LoadCmd
=
25
)
var
cmdStrings
=
[]
intName
{
{
uint32
(
LoadCmdSegment
),
"LoadCmdSegment"
},
{
uint32
(
LoadCmdSegment64
),
"LoadCmdSegment64"
},
{
uint32
(
LoadCmdThread
),
"LoadCmdThread"
},
{
uint32
(
LoadCmdUnixThread
),
"LoadCmdUnixThread"
},
{
uint32
(
LoadCmdDylib
),
"LoadCmdDylib"
},
{
uint32
(
LoadCmdSegment64
),
"LoadCmdSegment64"
},
}
func
(
i
LoadCmd
)
String
()
string
{
return
stringName
(
uint32
(
i
),
cmdStrings
,
false
)
}
...
...
@@ -104,6 +109,16 @@ type Segment32 struct {
Flag
uint32
}
// A DylibCmd is a Mach-O load dynamic library command.
type
DylibCmd
struct
{
Cmd
LoadCmd
Len
uint32
Name
uint32
Time
uint32
CurrentVersion
uint32
CompatVersion
uint32
}
// A Section32 is a 32-bit Mach-O section header.
type
Section32
struct
{
Name
[
16
]
byte
...
...
@@ -135,6 +150,67 @@ type Section64 struct {
Reserve3
uint32
}
// A SymtabCmd is a Mach-O symbol table command.
type
SymtabCmd
struct
{
Cmd
LoadCmd
Len
uint32
Symoff
uint32
Nsyms
uint32
Stroff
uint32
Strsize
uint32
}
// A DysymtabCmd is a Mach-O dynamic symbol table command.
type
DysymtabCmd
struct
{
Cmd
LoadCmd
Len
uint32
Ilocalsym
uint32
Nlocalsym
uint32
Iextdefsym
uint32
Nextdefsym
uint32
Iundefsym
uint32
Nundefsym
uint32
Tocoffset
uint32
Ntoc
uint32
Modtaboff
uint32
Nmodtab
uint32
Extrefsymoff
uint32
Nextrefsyms
uint32
Indirectsymoff
uint32
Nindirectsyms
uint32
Extreloff
uint32
Nextrel
uint32
Locreloff
uint32
Nlocrel
uint32
}
// An Nlist32 is a Mach-O 32-bit symbol table entry.
type
Nlist32
struct
{
Name
uint32
Type
uint8
Sect
uint8
Desc
uint16
Value
uint32
}
// An Nlist64 is a Mach-O 64-bit symbol table entry.
type
Nlist64
struct
{
Name
uint32
Type
uint8
Sect
uint8
Desc
uint16
Value
uint64
}
// A Symbol is a Mach-O 32-bit or 64-bit symbol table entry.
type
Symbol
struct
{
Name
string
Type
uint8
Sect
uint8
Desc
uint16
Value
uint64
}
// A Thread is a Mach-O thread state command.
type
Thread
struct
{
Cmd
LoadCmd
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment