Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
L
linux
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
linux
Commits
21d9540e
Commit
21d9540e
authored
Nov 05, 2002
by
Linus Torvalds
Browse files
Options
Browse Files
Download
Plain Diff
Merge
parents
ee4351da
d89f3847
Changes
13
Hide whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
521 additions
and
194 deletions
+521
-194
arch/i386/kernel/i387.c
arch/i386/kernel/i387.c
+37
-0
arch/i386/kernel/process.c
arch/i386/kernel/process.c
+20
-0
arch/ia64/kernel/process.c
arch/ia64/kernel/process.c
+58
-14
fs/binfmt_elf.c
fs/binfmt_elf.c
+244
-162
fs/exec.c
fs/exec.c
+36
-2
include/asm-i386/elf.h
include/asm-i386/elf.h
+20
-3
include/asm-ia64/elf.h
include/asm-ia64/elf.h
+12
-0
include/linux/elf.h
include/linux/elf.h
+3
-1
include/linux/elfcore.h
include/linux/elfcore.h
+41
-0
include/linux/sched.h
include/linux/sched.h
+8
-0
kernel/exit.c
kernel/exit.c
+22
-10
kernel/fork.c
kernel/fork.c
+4
-0
kernel/signal.c
kernel/signal.c
+16
-2
No files found.
arch/i386/kernel/i387.c
View file @
21d9540e
...
...
@@ -528,3 +528,40 @@ int dump_extended_fpu( struct pt_regs *regs, struct user_fxsr_struct *fpu )
return
fpvalid
;
}
int
dump_task_fpu
(
struct
task_struct
*
tsk
,
struct
user_i387_struct
*
fpu
)
{
int
fpvalid
=
tsk
->
used_math
;
if
(
fpvalid
)
{
if
(
tsk
==
current
)
unlazy_fpu
(
tsk
);
if
(
cpu_has_fxsr
)
copy_fpu_fxsave
(
tsk
,
fpu
);
else
copy_fpu_fsave
(
tsk
,
fpu
);
}
return
fpvalid
;
}
int
dump_task_extended_fpu
(
struct
task_struct
*
tsk
,
struct
user_fxsr_struct
*
fpu
)
{
int
fpvalid
=
tsk
->
used_math
&&
cpu_has_fxsr
;
if
(
fpvalid
)
{
if
(
tsk
==
current
)
unlazy_fpu
(
tsk
);
memcpy
(
fpu
,
&
tsk
->
thread
.
i387
.
fxsave
,
sizeof
(
*
fpu
));
}
return
fpvalid
;
}
#ifdef CONFIG_SMP
void
dump_smp_unlazy_fpu
(
void
)
{
unlazy_fpu
(
current
);
return
;
}
#endif
arch/i386/kernel/process.c
View file @
21d9540e
...
...
@@ -19,6 +19,7 @@
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/mm.h>
#include <linux/elfcore.h>
#include <linux/smp.h>
#include <linux/smp_lock.h>
#include <linux/stddef.h>
...
...
@@ -374,6 +375,25 @@ void dump_thread(struct pt_regs * regs, struct user * dump)
dump
->
u_fpvalid
=
dump_fpu
(
regs
,
&
dump
->
i387
);
}
/*
* Capture the user space registers if the task is not running (in user space)
*/
int
dump_task_regs
(
struct
task_struct
*
tsk
,
elf_gregset_t
*
regs
)
{
struct
pt_regs
ptregs
;
ptregs
=
*
(
struct
pt_regs
*
)
((
unsigned
long
)
tsk
->
thread_info
+
THREAD_SIZE
-
sizeof
(
ptregs
));
ptregs
.
xcs
&=
0xffff
;
ptregs
.
xds
&=
0xffff
;
ptregs
.
xes
&=
0xffff
;
ptregs
.
xss
&=
0xffff
;
elf_core_copy_regs
(
regs
,
&
ptregs
);
return
1
;
}
/*
* This special macro can be used to load a debugging register
*/
...
...
arch/ia64/kernel/process.c
View file @
21d9540e
...
...
@@ -383,6 +383,31 @@ copy_thread (int nr, unsigned long clone_flags,
void
do_copy_regs
(
struct
unw_frame_info
*
info
,
void
*
arg
)
{
do_copy_task_regs
(
current
,
info
,
arg
);
}
void
do_dump_fpu
(
struct
unw_frame_info
*
info
,
void
*
arg
)
{
do_dump_task_fpu
(
current
,
info
,
arg
);
}
void
ia64_elf_core_copy_regs
(
struct
pt_regs
*
pt
,
elf_gregset_t
dst
)
{
unw_init_running
(
do_copy_regs
,
dst
);
}
int
dump_fpu
(
struct
pt_regs
*
pt
,
elf_fpregset_t
dst
)
{
unw_init_running
(
do_dump_fpu
,
dst
);
return
1
;
/* f0-f31 are always valid so we always return 1 */
}
static
void
do_copy_task_regs
(
struct
task_struct
*
task
,
struct
unw_frame_info
*
info
,
void
*
arg
)
{
unsigned
long
mask
,
sp
,
nat_bits
=
0
,
ip
,
ar_rnat
,
urbs_end
,
cfm
;
elf_greg_t
*
dst
=
arg
;
...
...
@@ -398,12 +423,12 @@ do_copy_regs (struct unw_frame_info *info, void *arg)
unw_get_sp
(
info
,
&
sp
);
pt
=
(
struct
pt_regs
*
)
(
sp
+
16
);
urbs_end
=
ia64_get_user_rbs_end
(
current
,
pt
,
&
cfm
);
urbs_end
=
ia64_get_user_rbs_end
(
task
,
pt
,
&
cfm
);
if
(
ia64_sync_user_rbs
(
current
,
info
->
sw
,
pt
->
ar_bspstore
,
urbs_end
)
<
0
)
if
(
ia64_sync_user_rbs
(
task
,
info
->
sw
,
pt
->
ar_bspstore
,
urbs_end
)
<
0
)
return
;
ia64_peek
(
current
,
info
->
sw
,
urbs_end
,
(
long
)
ia64_rse_rnat_addr
((
long
*
)
urbs_end
),
ia64_peek
(
task
,
info
->
sw
,
urbs_end
,
(
long
)
ia64_rse_rnat_addr
((
long
*
)
urbs_end
),
&
ar_rnat
);
/*
...
...
@@ -452,7 +477,7 @@ do_copy_regs (struct unw_frame_info *info, void *arg)
}
void
do_dump_
fpu
(
struct
unw_frame_info
*
info
,
void
*
arg
)
do_dump_
task_fpu
(
struct
task_struct
*
task
,
struct
unw_frame_info
*
info
,
void
*
arg
)
{
elf_fpreg_t
*
dst
=
arg
;
int
i
;
...
...
@@ -467,22 +492,41 @@ do_dump_fpu (struct unw_frame_info *info, void *arg)
for
(
i
=
2
;
i
<
32
;
++
i
)
unw_get_fr
(
info
,
i
,
dst
+
i
);
ia64_flush_fph
(
current
);
if
((
current
->
thread
.
flags
&
IA64_THREAD_FPH_VALID
)
!=
0
)
memcpy
(
dst
+
32
,
current
->
thread
.
fph
,
96
*
16
);
ia64_flush_fph
(
task
);
if
((
task
->
thread
.
flags
&
IA64_THREAD_FPH_VALID
)
!=
0
)
memcpy
(
dst
+
32
,
task
->
thread
.
fph
,
96
*
16
);
}
void
ia64_elf_core_copy_regs
(
struct
pt_regs
*
pt
,
elf_gregset_t
dst
)
int
dump_task_regs
(
struct
task_struct
*
task
,
elf_gregset_t
*
regs
)
{
unw_init_running
(
do_copy_regs
,
dst
);
struct
unw_frame_info
tcore_info
;
if
(
current
==
task
)
{
unw_init_running
(
do_copy_regs
,
regs
);
}
else
{
memset
(
&
tcore_info
,
0
,
sizeof
(
tcore_info
));
unw_init_from_blocked_task
(
&
tcore_info
,
task
);
do_copy_task_regs
(
task
,
&
tcore_info
,
regs
);
}
return
1
;
}
int
dump_fpu
(
struct
pt_regs
*
pt
,
elf_fpregset_t
dst
)
int
dump_task_fpu
(
struct
task_struct
*
task
,
elf_fpregset_t
*
dst
)
{
unw_init_running
(
do_dump_fpu
,
dst
);
return
1
;
/* f0-f31 are always valid so we always return 1 */
struct
unw_frame_info
tcore_info
;
if
(
current
==
task
)
{
unw_init_running
(
do_dump_fpu
,
dst
);
}
else
{
memset
(
&
tcore_info
,
0
,
sizeof
(
tcore_info
));
unw_init_from_blocked_task
(
&
tcore_info
,
task
);
do_dump_task_fpu
(
task
,
&
tcore_info
,
dst
);
}
return
1
;
}
asmlinkage
long
...
...
fs/binfmt_elf.c
View file @
21d9540e
...
...
@@ -30,6 +30,7 @@
#include <linux/elfcore.h>
#include <linux/init.h>
#include <linux/highuid.h>
#include <linux/smp.h>
#include <linux/smp_lock.h>
#include <linux/compiler.h>
#include <linux/highmem.h>
...
...
@@ -534,9 +535,6 @@ static int load_elf_binary(struct linux_binprm * bprm, struct pt_regs * regs)
if
(
strcmp
(
elf_interpreter
,
"/usr/lib/libc.so.1"
)
==
0
||
strcmp
(
elf_interpreter
,
"/usr/lib/ld.so.1"
)
==
0
)
ibcs2_interpreter
=
1
;
#if 0
printk("Using ELF interpreter %s\n", elf_interpreter);
#endif
SET_PERSONALITY
(
elf_ex
,
ibcs2_interpreter
);
...
...
@@ -760,16 +758,6 @@ static int load_elf_binary(struct linux_binprm * bprm, struct pt_regs * regs)
padzero
(
elf_bss
);
#if 0
printk("(start_brk) %lx\n" , (long) current->mm->start_brk);
printk("(end_code) %lx\n" , (long) current->mm->end_code);
printk("(start_code) %lx\n" , (long) current->mm->start_code);
printk("(start_data) %lx\n" , (long) current->mm->start_data);
printk("(end_data) %lx\n" , (long) current->mm->end_data);
printk("(start_stack) %lx\n" , (long) current->mm->start_stack);
printk("(brk) %lx\n" , (long) current->mm->brk);
#endif
if
(
current
->
personality
&
MMAP_PAGE_ZERO
)
{
/* Why this, you ask??? Well SVr4 maps page 0 as read-only,
and some applications "depend" upon this behavior.
...
...
@@ -972,26 +960,6 @@ static int notesize(struct memelfnote *en)
return
sz
;
}
/* #define DEBUG */
#ifdef DEBUG
static
void
dump_regs
(
const
char
*
str
,
elf_greg_t
*
r
)
{
int
i
;
static
const
char
*
regs
[]
=
{
"ebx"
,
"ecx"
,
"edx"
,
"esi"
,
"edi"
,
"ebp"
,
"eax"
,
"ds"
,
"es"
,
"fs"
,
"gs"
,
"orig_eax"
,
"eip"
,
"cs"
,
"efl"
,
"uesp"
,
"ss"
};
printk
(
"Registers: %s
\n
"
,
str
);
for
(
i
=
0
;
i
<
ELF_NGREG
;
i
++
)
{
unsigned
long
val
=
r
[
i
];
printk
(
" %-2d %-5s=%08lx %lu
\n
"
,
i
,
regs
[
i
],
val
,
val
);
}
}
#endif
#define DUMP_WRITE(addr, nr) \
do { if (!dump_write(file, (addr), (nr))) return 0; } while(0)
#define DUMP_SEEK(off) \
...
...
@@ -1023,6 +991,164 @@ static int writenote(struct memelfnote *men, struct file *file)
#define DUMP_SEEK(off) \
if (!dump_seek(file, (off))) \
goto end_coredump;
static
inline
void
fill_elf_header
(
struct
elfhdr
*
elf
,
int
segs
)
{
memcpy
(
elf
->
e_ident
,
ELFMAG
,
SELFMAG
);
elf
->
e_ident
[
EI_CLASS
]
=
ELF_CLASS
;
elf
->
e_ident
[
EI_DATA
]
=
ELF_DATA
;
elf
->
e_ident
[
EI_VERSION
]
=
EV_CURRENT
;
memset
(
elf
->
e_ident
+
EI_PAD
,
0
,
EI_NIDENT
-
EI_PAD
);
elf
->
e_type
=
ET_CORE
;
elf
->
e_machine
=
ELF_ARCH
;
elf
->
e_version
=
EV_CURRENT
;
elf
->
e_entry
=
0
;
elf
->
e_phoff
=
sizeof
(
struct
elfhdr
);
elf
->
e_shoff
=
0
;
elf
->
e_flags
=
0
;
elf
->
e_ehsize
=
sizeof
(
struct
elfhdr
);
elf
->
e_phentsize
=
sizeof
(
struct
elf_phdr
);
elf
->
e_phnum
=
segs
;
elf
->
e_shentsize
=
0
;
elf
->
e_shnum
=
0
;
elf
->
e_shstrndx
=
0
;
return
;
}
static
inline
void
fill_elf_note_phdr
(
struct
elf_phdr
*
phdr
,
int
sz
,
off_t
offset
)
{
phdr
->
p_type
=
PT_NOTE
;
phdr
->
p_offset
=
offset
;
phdr
->
p_vaddr
=
0
;
phdr
->
p_paddr
=
0
;
phdr
->
p_filesz
=
sz
;
phdr
->
p_memsz
=
0
;
phdr
->
p_flags
=
0
;
phdr
->
p_align
=
0
;
return
;
}
static
inline
void
fill_note
(
struct
memelfnote
*
note
,
const
char
*
name
,
int
type
,
unsigned
int
sz
,
void
*
data
)
{
note
->
name
=
name
;
note
->
type
=
type
;
note
->
datasz
=
sz
;
note
->
data
=
data
;
return
;
}
/*
* fill up all the fields in prstatus from the given task struct, except registers
* which need to be filled up seperately.
*/
static
inline
void
fill_prstatus
(
struct
elf_prstatus
*
prstatus
,
struct
task_struct
*
p
,
long
signr
)
{
prstatus
->
pr_info
.
si_signo
=
prstatus
->
pr_cursig
=
signr
;
prstatus
->
pr_sigpend
=
p
->
pending
.
signal
.
sig
[
0
];
prstatus
->
pr_sighold
=
p
->
blocked
.
sig
[
0
];
prstatus
->
pr_pid
=
p
->
pid
;
prstatus
->
pr_ppid
=
p
->
parent
->
pid
;
prstatus
->
pr_pgrp
=
p
->
pgrp
;
prstatus
->
pr_sid
=
p
->
session
;
jiffies_to_timeval
(
p
->
utime
,
&
prstatus
->
pr_utime
);
jiffies_to_timeval
(
p
->
stime
,
&
prstatus
->
pr_stime
);
jiffies_to_timeval
(
p
->
cutime
,
&
prstatus
->
pr_cutime
);
jiffies_to_timeval
(
p
->
cstime
,
&
prstatus
->
pr_cstime
);
}
static
inline
void
fill_psinfo
(
struct
elf_prpsinfo
*
psinfo
,
struct
task_struct
*
p
)
{
int
i
,
len
;
/* first copy the parameters from user space */
memset
(
psinfo
,
0
,
sizeof
(
struct
elf_prpsinfo
));
len
=
p
->
mm
->
arg_end
-
p
->
mm
->
arg_start
;
if
(
len
>=
ELF_PRARGSZ
)
len
=
ELF_PRARGSZ
-
1
;
copy_from_user
(
&
psinfo
->
pr_psargs
,
(
const
char
*
)
p
->
mm
->
arg_start
,
len
);
for
(
i
=
0
;
i
<
len
;
i
++
)
if
(
psinfo
->
pr_psargs
[
i
]
==
0
)
psinfo
->
pr_psargs
[
i
]
=
' '
;
psinfo
->
pr_psargs
[
len
]
=
0
;
psinfo
->
pr_pid
=
p
->
pid
;
psinfo
->
pr_ppid
=
p
->
parent
->
pid
;
psinfo
->
pr_pgrp
=
p
->
pgrp
;
psinfo
->
pr_sid
=
p
->
session
;
i
=
p
->
state
?
ffz
(
~
p
->
state
)
+
1
:
0
;
psinfo
->
pr_state
=
i
;
psinfo
->
pr_sname
=
(
i
<
0
||
i
>
5
)
?
'.'
:
"RSDZTD"
[
i
];
psinfo
->
pr_zomb
=
psinfo
->
pr_sname
==
'Z'
;
psinfo
->
pr_nice
=
task_nice
(
p
);
psinfo
->
pr_flag
=
p
->
flags
;
psinfo
->
pr_uid
=
NEW_TO_OLD_UID
(
p
->
uid
);
psinfo
->
pr_gid
=
NEW_TO_OLD_GID
(
p
->
gid
);
strncpy
(
psinfo
->
pr_fname
,
p
->
comm
,
sizeof
(
psinfo
->
pr_fname
));
return
;
}
/* Here is the structure in which status of each thread is captured. */
struct
elf_thread_status
{
struct
list_head
list
;
struct
elf_prstatus
prstatus
;
/* NT_PRSTATUS */
elf_fpregset_t
fpu
;
/* NT_PRFPREG */
#ifdef ELF_CORE_COPY_XFPREGS
elf_fpxregset_t
xfpu
;
/* NT_PRXFPREG */
#endif
struct
memelfnote
notes
[
3
];
int
num_notes
;
};
/*
* In order to add the specific thread information for the elf file format,
* we need to keep a linked list of every threads pr_status and then
* create a single section for them in the final core file.
*/
static
int
elf_dump_thread_status
(
long
signr
,
struct
task_struct
*
p
,
struct
list_head
*
thread_list
)
{
struct
elf_thread_status
*
t
;
int
sz
=
0
;
t
=
kmalloc
(
sizeof
(
*
t
),
GFP_ATOMIC
);
if
(
!
t
)
return
0
;
memset
(
t
,
0
,
sizeof
(
*
t
));
INIT_LIST_HEAD
(
&
t
->
list
);
t
->
num_notes
=
0
;
fill_prstatus
(
&
t
->
prstatus
,
p
,
signr
);
elf_core_copy_task_regs
(
p
,
&
t
->
prstatus
.
pr_reg
);
fill_note
(
&
t
->
notes
[
0
],
"CORE"
,
NT_PRSTATUS
,
sizeof
(
t
->
prstatus
),
&
(
t
->
prstatus
));
t
->
num_notes
++
;
sz
+=
notesize
(
&
t
->
notes
[
0
]);
if
((
t
->
prstatus
.
pr_fpvalid
=
elf_core_copy_task_fpregs
(
p
,
&
t
->
fpu
)))
{
fill_note
(
&
t
->
notes
[
1
],
"CORE"
,
NT_PRFPREG
,
sizeof
(
t
->
fpu
),
&
(
t
->
fpu
));
t
->
num_notes
++
;
sz
+=
notesize
(
&
t
->
notes
[
1
]);
}
#ifdef ELF_CORE_COPY_XFPREGS
if
(
elf_core_copy_task_xfpregs
(
p
,
&
t
->
xfpu
))
{
fill_note
(
&
t
->
notes
[
2
],
"LINUX"
,
NT_PRXFPREG
,
sizeof
(
t
->
xfpu
),
&
t
->
xfpu
);
t
->
num_notes
++
;
sz
+=
notesize
(
&
t
->
notes
[
2
]);
}
#endif
list_add
(
&
t
->
list
,
thread_list
);
return
sz
;
}
/*
* Actual dumper
*
...
...
@@ -1041,74 +1167,56 @@ static int elf_core_dump(long signr, struct pt_regs * regs, struct file * file)
struct
elfhdr
elf
;
off_t
offset
=
0
,
dataoff
;
unsigned
long
limit
=
current
->
rlim
[
RLIMIT_CORE
].
rlim_cur
;
int
numnote
=
4
;
struct
memelfnote
notes
[
4
];
int
numnote
=
5
;
struct
memelfnote
notes
[
5
];
struct
elf_prstatus
prstatus
;
/* NT_PRSTATUS */
elf_fpregset_t
fpu
;
/* NT_PRFPREG */
struct
elf_prpsinfo
psinfo
;
/* NT_PRPSINFO */
/* first copy the parameters from user space */
memset
(
&
psinfo
,
0
,
sizeof
(
psinfo
));
{
int
i
,
len
;
len
=
current
->
mm
->
arg_end
-
current
->
mm
->
arg_start
;
if
(
len
>=
ELF_PRARGSZ
)
len
=
ELF_PRARGSZ
-
1
;
copy_from_user
(
&
psinfo
.
pr_psargs
,
(
const
char
*
)
current
->
mm
->
arg_start
,
len
);
for
(
i
=
0
;
i
<
len
;
i
++
)
if
(
psinfo
.
pr_psargs
[
i
]
==
0
)
psinfo
.
pr_psargs
[
i
]
=
' '
;
psinfo
.
pr_psargs
[
len
]
=
0
;
}
memset
(
&
prstatus
,
0
,
sizeof
(
prstatus
));
/*
* This transfers the registers from regs into the standard
* coredump arrangement, whatever that is.
struct
task_struct
*
g
,
*
p
;
LIST_HEAD
(
thread_list
);
struct
list_head
*
t
;
elf_fpregset_t
fpu
;
#ifdef ELF_CORE_COPY_XFPREGS
elf_fpxregset_t
xfpu
;
#endif
int
thread_status_size
=
0
;
/* We no longer stop all vm operations
*
* This because those proceses that could possibly
* change map_count or the mmap / vma pages are now blocked in do_exit on current finishing
* this core dump.
*
* Only ptrace can touch these memory addresses, but it doesn't change
* the map_count or the pages allocated. So no possibility of crashing exists while dumping
* the mm->vm_next areas to the core file.
*
*/
#ifdef ELF_CORE_COPY_REGS
ELF_CORE_COPY_REGS
(
prstatus
.
pr_reg
,
regs
)
#else
if
(
sizeof
(
elf_gregset_t
)
!=
sizeof
(
struct
pt_regs
))
{
printk
(
"sizeof(elf_gregset_t) (%ld) != sizeof(struct pt_regs) (%ld)
\n
"
,
(
long
)
sizeof
(
elf_gregset_t
),
(
long
)
sizeof
(
struct
pt_regs
));
/* capture the status of all other threads */
if
(
signr
)
{
read_lock
(
&
tasklist_lock
);
do_each_thread
(
g
,
p
)
if
(
current
->
mm
==
p
->
mm
&&
current
!=
p
)
{
int
sz
=
elf_dump_thread_status
(
signr
,
p
,
&
thread_list
);
if
(
!
sz
)
{
read_unlock
(
&
tasklist_lock
);
goto
cleanup
;
}
else
thread_status_size
+=
sz
;
}
while_each_thread
(
g
,
p
);
read_unlock
(
&
tasklist_lock
);
}
else
*
(
struct
pt_regs
*
)
&
prstatus
.
pr_reg
=
*
regs
;
#endif
/* now stop all vm operations */
down_write
(
&
current
->
mm
->
mmap_sem
);
/* now collect the dump for the current */
memset
(
&
prstatus
,
0
,
sizeof
(
prstatus
));
fill_prstatus
(
&
prstatus
,
current
,
signr
);
elf_core_copy_regs
(
&
prstatus
.
pr_reg
,
regs
);
segs
=
current
->
mm
->
map_count
;
#ifdef DEBUG
printk
(
"elf_core_dump: %d segs %lu limit
\n
"
,
segs
,
limit
);
#endif
/* Set up header */
memcpy
(
elf
.
e_ident
,
ELFMAG
,
SELFMAG
);
elf
.
e_ident
[
EI_CLASS
]
=
ELF_CLASS
;
elf
.
e_ident
[
EI_DATA
]
=
ELF_DATA
;
elf
.
e_ident
[
EI_VERSION
]
=
EV_CURRENT
;
memset
(
elf
.
e_ident
+
EI_PAD
,
0
,
EI_NIDENT
-
EI_PAD
);
elf
.
e_type
=
ET_CORE
;
elf
.
e_machine
=
ELF_ARCH
;
elf
.
e_version
=
EV_CURRENT
;
elf
.
e_entry
=
0
;
elf
.
e_phoff
=
sizeof
(
elf
);
elf
.
e_shoff
=
0
;
elf
.
e_flags
=
0
;
elf
.
e_ehsize
=
sizeof
(
elf
);
elf
.
e_phentsize
=
sizeof
(
struct
elf_phdr
);
elf
.
e_phnum
=
segs
+
1
;
/* Include notes */
elf
.
e_shentsize
=
0
;
elf
.
e_shnum
=
0
;
elf
.
e_shstrndx
=
0
;
fill_elf_header
(
&
elf
,
segs
+
1
);
/* including notes section*/
fs
=
get_fs
();
set_fs
(
KERNEL_DS
);
...
...
@@ -1125,60 +1233,27 @@ static int elf_core_dump(long signr, struct pt_regs * regs, struct file * file)
* with info from their /proc.
*/
notes
[
0
].
name
=
"CORE"
;
notes
[
0
].
type
=
NT_PRSTATUS
;
notes
[
0
].
datasz
=
sizeof
(
prstatus
);
notes
[
0
].
data
=
&
prstatus
;
prstatus
.
pr_info
.
si_signo
=
prstatus
.
pr_cursig
=
signr
;
prstatus
.
pr_sigpend
=
current
->
pending
.
signal
.
sig
[
0
];
prstatus
.
pr_sighold
=
current
->
blocked
.
sig
[
0
];
psinfo
.
pr_pid
=
prstatus
.
pr_pid
=
current
->
pid
;
psinfo
.
pr_ppid
=
prstatus
.
pr_ppid
=
current
->
parent
->
pid
;
psinfo
.
pr_pgrp
=
prstatus
.
pr_pgrp
=
current
->
pgrp
;
psinfo
.
pr_sid
=
prstatus
.
pr_sid
=
current
->
session
;
jiffies_to_timeval
(
current
->
utime
,
&
prstatus
.
pr_utime
);
jiffies_to_timeval
(
current
->
stime
,
&
prstatus
.
pr_stime
);
jiffies_to_timeval
(
current
->
cutime
,
&
prstatus
.
pr_cutime
);
jiffies_to_timeval
(
current
->
cstime
,
&
prstatus
.
pr_cstime
);
#ifdef DEBUG
dump_regs
(
"Passed in regs"
,
(
elf_greg_t
*
)
regs
);
dump_regs
(
"prstatus regs"
,
(
elf_greg_t
*
)
&
prstatus
.
pr_reg
);
#endif
notes
[
1
].
name
=
"CORE"
;
notes
[
1
].
type
=
NT_PRPSINFO
;
notes
[
1
].
datasz
=
sizeof
(
psinfo
);
notes
[
1
].
data
=
&
psinfo
;
i
=
current
->
state
?
ffz
(
~
current
->
state
)
+
1
:
0
;
psinfo
.
pr_state
=
i
;
psinfo
.
pr_sname
=
(
i
<
0
||
i
>
5
)
?
'.'
:
"RSDZTD"
[
i
];
psinfo
.
pr_zomb
=
psinfo
.
pr_sname
==
'Z'
;
psinfo
.
pr_nice
=
task_nice
(
current
);
psinfo
.
pr_flag
=
current
->
flags
;
psinfo
.
pr_uid
=
NEW_TO_OLD_UID
(
current
->
uid
);
psinfo
.
pr_gid
=
NEW_TO_OLD_GID
(
current
->
gid
);
strncpy
(
psinfo
.
pr_fname
,
current
->
comm
,
sizeof
(
psinfo
.
pr_fname
));
notes
[
2
].
name
=
"CORE"
;
notes
[
2
].
type
=
NT_TASKSTRUCT
;
notes
[
2
].
datasz
=
sizeof
(
*
current
);
notes
[
2
].
data
=
current
;
/* Try to dump the FPU. */
prstatus
.
pr_fpvalid
=
dump_fpu
(
regs
,
&
fpu
);
if
(
!
prstatus
.
pr_fpvalid
)
{
numnote
--
;
}
else
{
notes
[
3
].
name
=
"CORE"
;
notes
[
3
].
type
=
NT_PRFPREG
;
notes
[
3
].
datasz
=
sizeof
(
fpu
);
notes
[
3
].
data
=
&
fpu
;
}
fill_note
(
&
notes
[
0
],
"CORE"
,
NT_PRSTATUS
,
sizeof
(
prstatus
),
&
prstatus
);
fill_psinfo
(
&
psinfo
,
current
->
group_leader
);
fill_note
(
&
notes
[
1
],
"CORE"
,
NT_PRPSINFO
,
sizeof
(
psinfo
),
&
psinfo
);
fill_note
(
&
notes
[
2
],
"CORE"
,
NT_TASKSTRUCT
,
sizeof
(
*
current
),
current
);
/* Try to dump the FPU. */
if
((
prstatus
.
pr_fpvalid
=
elf_core_copy_task_fpregs
(
current
,
&
fpu
)))
fill_note
(
&
notes
[
3
],
"CORE"
,
NT_PRFPREG
,
sizeof
(
fpu
),
&
fpu
);
else
--
numnote
;
#ifdef ELF_CORE_COPY_XFPREGS
if
(
elf_core_copy_task_xfpregs
(
current
,
&
xfpu
))
fill_note
(
&
notes
[
4
],
"LINUX"
,
NT_PRXFPREG
,
sizeof
(
xfpu
),
&
xfpu
);
else
--
numnote
;
#else
numnote
--
;
#endif
/* Write notes phdr entry */
{
struct
elf_phdr
phdr
;
...
...
@@ -1186,17 +1261,11 @@ static int elf_core_dump(long signr, struct pt_regs * regs, struct file * file)
for
(
i
=
0
;
i
<
numnote
;
i
++
)
sz
+=
notesize
(
&
notes
[
i
]);
sz
+=
thread_status_size
;
phdr
.
p_type
=
PT_NOTE
;
phdr
.
p_offset
=
offset
;
phdr
.
p_vaddr
=
0
;
phdr
.
p_paddr
=
0
;
phdr
.
p_filesz
=
sz
;
phdr
.
p_memsz
=
0
;
phdr
.
p_flags
=
0
;
phdr
.
p_align
=
0
;
offset
+=
phdr
.
p_filesz
;
fill_elf_note_phdr
(
&
phdr
,
sz
,
offset
);
offset
+=
sz
;
DUMP_WRITE
(
&
phdr
,
sizeof
(
phdr
));
}
...
...
@@ -1225,10 +1294,19 @@ static int elf_core_dump(long signr, struct pt_regs * regs, struct file * file)
DUMP_WRITE
(
&
phdr
,
sizeof
(
phdr
));
}
/* write out the notes section */
for
(
i
=
0
;
i
<
numnote
;
i
++
)
if
(
!
writenote
(
&
notes
[
i
],
file
))
goto
end_coredump
;
/* write out the thread status notes section */
list_for_each
(
t
,
&
thread_list
)
{
struct
elf_thread_status
*
tmp
=
list_entry
(
t
,
struct
elf_thread_status
,
list
);
for
(
i
=
0
;
i
<
tmp
->
num_notes
;
i
++
)
if
(
!
writenote
(
&
tmp
->
notes
[
i
],
file
))
goto
end_coredump
;
}
DUMP_SEEK
(
dataoff
);
for
(
vma
=
current
->
mm
->
mmap
;
vma
!=
NULL
;
vma
=
vma
->
vm_next
)
{
...
...
@@ -1237,10 +1315,6 @@ static int elf_core_dump(long signr, struct pt_regs * regs, struct file * file)
if
(
!
maydump
(
vma
))
continue
;
#ifdef DEBUG
printk
(
"elf_core_dump: writing %08lx-%08lx
\n
"
,
vma
->
vm_start
,
vma
->
vm_end
);
#endif
for
(
addr
=
vma
->
vm_start
;
addr
<
vma
->
vm_end
;
addr
+=
PAGE_SIZE
)
{
...
...
@@ -1272,11 +1346,19 @@ static int elf_core_dump(long signr, struct pt_regs * regs, struct file * file)
(
off_t
)
file
->
f_pos
,
offset
);
}
end_coredump:
end_coredump:
set_fs
(
fs
);
up_write
(
&
current
->
mm
->
mmap_sem
);
cleanup:
while
(
!
list_empty
(
&
thread_list
))
{
struct
list_head
*
tmp
=
thread_list
.
next
;
list_del
(
tmp
);
kfree
(
list_entry
(
tmp
,
struct
elf_thread_status
,
list
));
}
return
has_dumped
;
}
#endif
/* USE_ELF_CORE_DUMP */
static
int
__init
init_elf_binfmt
(
void
)
...
...
fs/exec.c
View file @
21d9540e
...
...
@@ -1209,6 +1209,35 @@ void format_corename(char *corename, const char *pattern, long signr)
*
out_ptr
=
0
;
}
static
void
zap_threads
(
struct
mm_struct
*
mm
)
{
struct
task_struct
*
g
,
*
p
;
/* give other threads a chance to run: */
yield
();
read_lock
(
&
tasklist_lock
);
do_each_thread
(
g
,
p
)
if
(
mm
==
p
->
mm
&&
!
p
->
core_waiter
)
force_sig_specific
(
SIGKILL
,
p
);
while_each_thread
(
g
,
p
);
read_unlock
(
&
tasklist_lock
);
}
static
void
coredump_wait
(
struct
mm_struct
*
mm
)
{
DECLARE_WAITQUEUE
(
wait
,
current
);
atomic_inc
(
&
mm
->
core_waiters
);
add_wait_queue
(
&
mm
->
core_wait
,
&
wait
);
zap_threads
(
mm
);
current
->
state
=
TASK_UNINTERRUPTIBLE
;
if
(
atomic_read
(
&
mm
->
core_waiters
)
!=
atomic_read
(
&
mm
->
mm_users
))
schedule
();
else
current
->
state
=
TASK_RUNNING
;
}
int
do_coredump
(
long
signr
,
struct
pt_regs
*
regs
)
{
struct
linux_binfmt
*
binfmt
;
...
...
@@ -1224,13 +1253,16 @@ int do_coredump(long signr, struct pt_regs * regs)
if
(
!
current
->
mm
->
dumpable
)
goto
fail
;
current
->
mm
->
dumpable
=
0
;
if
(
down_trylock
(
&
current
->
mm
->
core_sem
))
BUG
();
coredump_wait
(
current
->
mm
);
if
(
current
->
rlim
[
RLIMIT_CORE
].
rlim_cur
<
binfmt
->
min_coredump
)
goto
fail
;
goto
fail
_unlock
;
format_corename
(
corename
,
core_pattern
,
signr
);
file
=
filp_open
(
corename
,
O_CREAT
|
2
|
O_NOFOLLOW
,
0600
);
if
(
IS_ERR
(
file
))
goto
fail
;
goto
fail
_unlock
;
inode
=
file
->
f_dentry
->
d_inode
;
if
(
inode
->
i_nlink
>
1
)
goto
close_fail
;
/* multiple links - don't dump */
...
...
@@ -1250,6 +1282,8 @@ int do_coredump(long signr, struct pt_regs * regs)
close_fail:
filp_close
(
file
,
NULL
);
fail_unlock:
up
(
&
current
->
mm
->
core_sem
);
fail:
unlock_kernel
();
return
retval
;
...
...
include/asm-i386/elf.h
View file @
21d9540e
...
...
@@ -7,6 +7,7 @@
#include <asm/ptrace.h>
#include <asm/user.h>
#include <asm/processor.h>
#include <linux/utsname.h>
...
...
@@ -59,6 +60,9 @@ typedef struct user_fxsr_struct elf_fpxregset_t;
/* Wow, the "main" arch needs arch dependent functions too.. :) */
#define savesegment(seg,value) \
asm volatile("movl %%" #seg ",%0":"=m" (*(int *)&(value)))
/* regs is struct pt_regs, pr_reg is elf_gregset_t (which is
now struct_user_regs, they are different) */
...
...
@@ -72,9 +76,8 @@ typedef struct user_fxsr_struct elf_fpxregset_t;
pr_reg[6] = regs->eax; \
pr_reg[7] = regs->xds; \
pr_reg[8] = regs->xes; \
/* fake once used fs and gs selectors? */
\
pr_reg[9] = regs->xds;
/* was fs and __fs */
\
pr_reg[10] = regs->xds;
/* was gs and __gs */
\
savesegment(fs,pr_reg[9]); \
savesegment(gs,pr_reg[10]); \
pr_reg[11] = regs->orig_eax; \
pr_reg[12] = regs->eip; \
pr_reg[13] = regs->xcs; \
...
...
@@ -99,6 +102,20 @@ typedef struct user_fxsr_struct elf_fpxregset_t;
#ifdef __KERNEL__
#define SET_PERSONALITY(ex, ibcs2) set_personality((ibcs2)?PER_SVR4:PER_LINUX)
extern
int
dump_task_regs
(
struct
task_struct
*
,
elf_gregset_t
*
);
extern
int
dump_task_fpu
(
struct
task_struct
*
,
elf_fpregset_t
*
);
extern
int
dump_task_extended_fpu
(
struct
task_struct
*
,
struct
user_fxsr_struct
*
);
#define ELF_CORE_COPY_TASK_REGS(tsk, elf_regs) dump_task_regs(tsk, elf_regs)
#define ELF_CORE_COPY_FPREGS(tsk, elf_fpregs) dump_task_fpu(tsk, elf_fpregs)
#define ELF_CORE_COPY_XFPREGS(tsk, elf_xfpregs) dump_task_extended_fpu(tsk, elf_xfpregs)
#ifdef CONFIG_SMP
extern
void
dump_smp_unlazy_fpu
(
void
);
#define ELF_CORE_SYNC dump_smp_unlazy_fpu
#endif
#endif
#endif
include/asm-ia64/elf.h
View file @
21d9540e
...
...
@@ -65,12 +65,16 @@ extern void ia64_init_addr_space (void);
#define ELF_NGREG 128
/* we really need just 72 but let's leave some headroom... */
#define ELF_NFPREG 128
/* f0 and f1 could be omitted, but so what... */
typedef
unsigned
long
elf_fpxregset_t
;
typedef
unsigned
long
elf_greg_t
;
typedef
elf_greg_t
elf_gregset_t
[
ELF_NGREG
];
typedef
struct
ia64_fpreg
elf_fpreg_t
;
typedef
elf_fpreg_t
elf_fpregset_t
[
ELF_NFPREG
];
struct
pt_regs
;
/* forward declaration... */
extern
void
ia64_elf_core_copy_regs
(
struct
pt_regs
*
src
,
elf_gregset_t
dst
);
#define ELF_CORE_COPY_REGS(_dest,_regs) ia64_elf_core_copy_regs(_regs, _dest);
...
...
@@ -88,6 +92,14 @@ extern void ia64_elf_core_copy_regs (struct pt_regs *src, elf_gregset_t dst);
struct
elf64_hdr
;
extern
void
ia64_set_personality
(
struct
elf64_hdr
*
elf_ex
,
int
ibcs2_interpreter
);
#define SET_PERSONALITY(ex, ibcs2) ia64_set_personality(&(ex), ibcs2)
extern
int
dump_task_regs
(
struct
task_struct
*
,
elf_gregset_t
*
);
extern
int
dump_task_fpu
(
struct
task_struct
*
,
elf_fpregset_t
*
);
#define ELF_CORE_COPY_TASK_REGS(tsk, elf_gregs) dump_task_regs(tsk, elf_gregs)
#define ELF_CORE_COPY_FPREGS(tsk, elf_fpregs) dump_task_fpu(tsk, elf_fpregs)
#endif
#endif
/* _ASM_IA64_ELF_H */
include/linux/elf.h
View file @
21d9540e
#ifndef _LINUX_ELF_H
#define _LINUX_ELF_H
#include <linux/sched.h>
#include <linux/types.h>
#include <asm/elf.h>
...
...
@@ -578,7 +579,8 @@ typedef struct elf64_shdr {
#define NT_PRFPREG 2
#define NT_PRPSINFO 3
#define NT_TASKSTRUCT 4
#define NT_PRFPXREG 20
#define NT_PRXFPREG 0x46e62b7f
/* copied from gdb5.1/include/elf/common.h */
/* Note header in a PT_NOTE section */
typedef
struct
elf32_note
{
...
...
include/linux/elfcore.h
View file @
21d9540e
...
...
@@ -85,4 +85,45 @@ typedef struct elf_prpsinfo prpsinfo_t;
#define PRARGSZ ELF_PRARGSZ
#endif
#ifdef __KERNEL__
static
inline
void
elf_core_copy_regs
(
elf_gregset_t
*
elfregs
,
struct
pt_regs
*
regs
)
{
#ifdef ELF_CORE_COPY_REGS
ELF_CORE_COPY_REGS
((
*
elfregs
),
regs
)
#else
BUG_ON
(
sizeof
(
*
elfregs
)
!=
sizeof
(
*
regs
));
*
(
struct
pt_regs
*
)
elfregs
=
*
regs
;
#endif
}
static
inline
int
elf_core_copy_task_regs
(
struct
task_struct
*
t
,
elf_gregset_t
*
elfregs
)
{
#ifdef ELF_CORE_COPY_TASK_REGS
return
ELF_CORE_COPY_TASK_REGS
(
t
,
elfregs
);
#endif
return
0
;
}
extern
int
dump_fpu
(
struct
pt_regs
*
,
elf_fpregset_t
*
);
static
inline
int
elf_core_copy_task_fpregs
(
struct
task_struct
*
t
,
elf_fpregset_t
*
fpu
)
{
#ifdef ELF_CORE_COPY_FPREGS
return
ELF_CORE_COPY_FPREGS
(
t
,
fpu
);
#else
return
dump_fpu
(
NULL
,
fpu
);
#endif
}
#ifdef ELF_CORE_COPY_XFPREGS
static
inline
int
elf_core_copy_task_xfpregs
(
struct
task_struct
*
t
,
elf_fpxregset_t
*
xfpu
)
{
return
ELF_CORE_COPY_XFPREGS
(
t
,
xfpu
);
}
#endif
#endif
/* __KERNEL__ */
#endif
/* _LINUX_ELFCORE_H */
include/linux/sched.h
View file @
21d9540e
...
...
@@ -198,6 +198,11 @@ struct mm_struct {
/* Architecture-specific MM context */
mm_context_t
context
;
/* coredumping support */
struct
semaphore
core_sem
;
atomic_t
core_waiters
;
wait_queue_head_t
core_wait
;
/* aio bits */
rwlock_t
ioctx_list_lock
;
struct
kioctx
*
ioctx_list
;
...
...
@@ -388,6 +393,8 @@ struct task_struct {
void
*
journal_info
;
struct
dentry
*
proc_dentry
;
struct
backing_dev_info
*
backing_dev_info
;
/* threaded coredumping support */
int
core_waiter
;
unsigned
long
ptrace_message
;
};
...
...
@@ -532,6 +539,7 @@ extern int kill_proc_info(int, struct siginfo *, pid_t);
extern
void
notify_parent
(
struct
task_struct
*
,
int
);
extern
void
do_notify_parent
(
struct
task_struct
*
,
int
);
extern
void
force_sig
(
int
,
struct
task_struct
*
);
extern
void
force_sig_specific
(
int
,
struct
task_struct
*
);
extern
int
send_sig
(
int
,
struct
task_struct
*
,
int
);
extern
int
__broadcast_thread_group
(
struct
task_struct
*
p
,
int
sig
);
extern
int
kill_pg
(
pid_t
,
int
,
int
);
...
...
kernel/exit.c
View file @
21d9540e
...
...
@@ -416,19 +416,31 @@ void end_lazy_tlb(struct mm_struct *mm)
*/
static
inline
void
__exit_mm
(
struct
task_struct
*
tsk
)
{
struct
mm_struct
*
mm
=
tsk
->
mm
;
struct
mm_struct
*
mm
=
tsk
->
mm
;
mm_release
();
if
(
mm
)
{
atomic_inc
(
&
mm
->
mm_count
);
if
(
mm
!=
tsk
->
active_mm
)
BUG
();
/* more a memory barrier than a real lock */
task_lock
(
tsk
);
tsk
->
mm
=
NULL
;
enter_lazy_tlb
(
mm
,
current
,
smp_processor_id
());
task_unlock
(
tsk
);
mmput
(
mm
);
if
(
!
mm
)
return
;
/*
* Serialize with any possible pending coredump:
*/
if
(
!
mm
->
dumpable
)
{
current
->
core_waiter
=
1
;
atomic_inc
(
&
mm
->
core_waiters
);
if
(
atomic_read
(
&
mm
->
core_waiters
)
==
atomic_read
(
&
mm
->
mm_users
))
wake_up
(
&
mm
->
core_wait
);
down
(
&
mm
->
core_sem
);
up
(
&
mm
->
core_sem
);
atomic_dec
(
&
mm
->
core_waiters
);
}
atomic_inc
(
&
mm
->
mm_count
);
if
(
mm
!=
tsk
->
active_mm
)
BUG
();
/* more a memory barrier than a real lock */
task_lock
(
tsk
);
tsk
->
mm
=
NULL
;
enter_lazy_tlb
(
mm
,
current
,
smp_processor_id
());
task_unlock
(
tsk
);
mmput
(
mm
);
}
void
exit_mm
(
struct
task_struct
*
tsk
)
...
...
kernel/fork.c
View file @
21d9540e
...
...
@@ -306,6 +306,9 @@ static struct mm_struct * mm_init(struct mm_struct * mm)
atomic_set
(
&
mm
->
mm_users
,
1
);
atomic_set
(
&
mm
->
mm_count
,
1
);
init_rwsem
(
&
mm
->
mmap_sem
);
init_MUTEX
(
&
mm
->
core_sem
);
init_waitqueue_head
(
&
mm
->
core_wait
);
atomic_set
(
&
mm
->
core_waiters
,
0
);
mm
->
page_table_lock
=
SPIN_LOCK_UNLOCKED
;
mm
->
ioctx_list_lock
=
RW_LOCK_UNLOCKED
;
mm
->
default_kioctx
=
(
struct
kioctx
)
INIT_KIOCTX
(
mm
->
default_kioctx
,
*
mm
);
...
...
@@ -772,6 +775,7 @@ static struct task_struct *copy_process(unsigned long clone_flags,
p
->
start_time
=
jiffies
;
p
->
security
=
NULL
;
p
->
core_waiter
=
0
;
retval
=
-
ENOMEM
;
if
(
security_ops
->
task_alloc_security
(
p
))
goto
bad_fork_cleanup
;
...
...
kernel/signal.c
View file @
21d9540e
...
...
@@ -768,7 +768,7 @@ force_sig_info(int sig, struct siginfo *info, struct task_struct *t)
}
static
int
specific_force_sig_info
(
int
sig
,
struct
task_struct
*
t
)
__
specific_force_sig_info
(
int
sig
,
struct
task_struct
*
t
)
{
if
(
!
t
->
sig
)
return
-
ESRCH
;
...
...
@@ -781,6 +781,20 @@ specific_force_sig_info(int sig, struct task_struct *t)
return
specific_send_sig_info
(
sig
,
(
void
*
)
2
,
t
,
0
);
}
void
force_sig_specific
(
int
sig
,
struct
task_struct
*
t
)
{
unsigned
long
int
flags
;
spin_lock_irqsave
(
&
t
->
sig
->
siglock
,
flags
);
if
(
t
->
sig
->
action
[
sig
-
1
].
sa
.
sa_handler
==
SIG_IGN
)
t
->
sig
->
action
[
sig
-
1
].
sa
.
sa_handler
=
SIG_DFL
;
sigdelset
(
&
t
->
blocked
,
sig
);
recalc_sigpending_tsk
(
t
);
specific_send_sig_info
(
sig
,
(
void
*
)
2
,
t
,
0
);
spin_unlock_irqrestore
(
&
t
->
sig
->
siglock
,
flags
);
}
#define can_take_signal(p, sig) \
(((unsigned long) p->sig->action[sig-1].sa.sa_handler > 1) && \
!sigismember(&p->blocked, sig) && (task_curr(p) || !signal_pending(p)))
...
...
@@ -846,7 +860,7 @@ int __broadcast_thread_group(struct task_struct *p, int sig)
int
err
=
0
;
for_each_task_pid
(
p
->
tgid
,
PIDTYPE_TGID
,
tmp
,
l
,
pid
)
err
=
specific_force_sig_info
(
sig
,
tmp
);
err
=
__
specific_force_sig_info
(
sig
,
tmp
);
return
err
;
}
...
...
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