ptrace: DEBUGCTLMSR_BTF fixes
v2 cover ptrace: DEBUGCTLMSR_BTF fixes
Hello.
The subject says "ptrace:", but we need these changes for uprobes.
Although I think this fix makes sense by itself. I am really surprised how many problems this simple code has, or I am totally confused (this is very possible).
Could someone review 2/2 ?
The next step will change uprobes to use the new helper and avoid user_enable_single_step().
Oleg.
PATCH v2 1/2 ptrace: introduce set_task_blockstep() helper
No functional changes, preparation for the next fix and for uprobes single-step fixes.
Move the code playing with TIF_BLOCKSTEP/DEBUGCTLMSR_BTF into the new helper, set_task_blockstep().
--- arch/x86/kernel/step.c | 41 +++++++++++++++++++++-------------------- 1 files changed, 21 insertions(+), 20 deletions(-)
arch/x86/kernel/step.c +21 -20
@@ -157,6 +157,21 @@ static int enable_single_step(struct task_struct *child)return 1 ; } +static void set_task_blockstep (struct task_struct *task,bool on) +{ +unsigned long debugctl; + + debugctl =get_debugctlmsr (); +if (on) { + debugctl |=DEBUGCTLMSR_BTF ; +set_tsk_thread_flag (task,TIF_BLOCKSTEP ); + }else { + debugctl &= ~DEBUGCTLMSR_BTF ; +clear_tsk_thread_flag (task,TIF_BLOCKSTEP ); + } +update_debugctlmsr (debugctl); +} +/* * Enable single or block step. */
@@ -169,19 +184,10 @@ static void enable_step(struct task_struct *child, bool block)* So no one should try to use debugger block stepping in a program * that uses user-mode single stepping itself. */ -if (enable_single_step (child) && block) { -unsigned long debugctl =get_debugctlmsr (); - - debugctl |=DEBUGCTLMSR_BTF ; -update_debugctlmsr (debugctl); -set_tsk_thread_flag (child,TIF_BLOCKSTEP ); - }else if (test_tsk_thread_flag (child,TIF_BLOCKSTEP )) { -unsigned long debugctl =get_debugctlmsr (); - - debugctl &= ~DEBUGCTLMSR_BTF ; -update_debugctlmsr (debugctl); -clear_tsk_thread_flag (child,TIF_BLOCKSTEP ); - } +if (enable_single_step (child) && block) +set_task_blockstep (child, true); +else if (test_tsk_thread_flag (child,TIF_BLOCKSTEP )) +set_task_blockstep (child, false); }void user_enable_single_step (struct task_struct *child)
@@ -199,13 +205,8 @@ void user_disable_single_step(struct task_struct *child)/* * Make sure block stepping (BTF) is disabled. */ -if (test_tsk_thread_flag (child,TIF_BLOCKSTEP )) { -unsigned long debugctl =get_debugctlmsr (); - - debugctl &= ~DEBUGCTLMSR_BTF ; -update_debugctlmsr (debugctl); -clear_tsk_thread_flag (child,TIF_BLOCKSTEP ); - } +if (test_tsk_thread_flag (child,TIF_BLOCKSTEP )) +set_task_blockstep (child, false);/* Always clear TIF_SINGLESTEP... */ clear_tsk_thread_flag (child,TIF_SINGLESTEP );
Signature · 1 line
1.5.5.1
PATCH v2 2/2 ptrace: partly fix set_task_blockstep()->update_debugctlmsr() logic
In reply to Oleg NesterovAfaics the usage of update_debugctlmsr() and TIF_BLOCKSTEP in step.c was always very wrong.
1. update_debugctlmsr() was simply unneeded. The child sleeps TASK_TRACED, __switch_to_xtra(next_p => child) should notice TIF_BLOCKSTEP and set/clear DEBUGCTLMSR_BTF after resume if needed.
2. It is wrong. The state of DEBUGCTLMSR_BTF bit in CPU register should always match the state of current's TIF_BLOCKSTEP bit.
3. Even get_debugctlmsr() + update_debugctlmsr() itself does not look right. Irq can change other bits in MSR_IA32_DEBUGCTLMSR register or the caller can be preempted in between.
4. It is not safe to play with TIF_BLOCKSTEP if task != current. DEBUGCTLMSR_BTF and TIF_BLOCKSTEP should always match each other if the task is running. The tracee is stopped but it can be SIGKILL'ed right before set/clear_tsk_thread_flag().
However, now that uprobes uses user_enable_single_step(current) we can't simply remove update_debugctlmsr(). So this patch adds the additional "task == current" check and disables irqs to avoid the race with interrupts/preemption.
Unfortunately this patch doesn't solve the last problem, we need another fix. Probably we should teach ptrace_stop() to set/clear single/block stepping after resume.
And afaics there is yet another problem: perf can play with MSR_IA32_DEBUGCTLMSR from nmi, this obviously means that even __switch_to_xtra() has problems.
--- arch/x86/kernel/step.c | 14 +++++++++++++- 1 files changed, 13 insertions(+), 1 deletions(-)
arch/x86/kernel/step.c +13 -1
@@ -161,6 +161,16 @@ static void set_task_blockstep(struct task_struct *task, bool on) {unsigned long debugctl; +/* +* Ensure irq/preemption can't change debugctl in between. +* Note also that both TIF_BLOCKSTEP and debugctl should +* be changed atomically wrt preemption. +* FIXME: this means that set/clear TIF_BLOCKSTEP is simply +* wrong if task != current, SIGKILL can wakeup the stopped +* tracee and set/clear can play with the running task, this +* can confuse the next __switch_to_xtra(). +*/ +local_irq_disable (); debugctl =get_debugctlmsr ();if (on) { debugctl |=DEBUGCTLMSR_BTF ;
@@ -169,7 +179,9 @@ static void set_task_blockstep(struct task_struct *task, bool on) debugctl &= ~DEBUGCTLMSR_BTF ;clear_tsk_thread_flag (task,TIF_BLOCKSTEP ); } -update_debugctlmsr (debugctl); +if (task == current) +update_debugctlmsr (debugctl); +local_irq_enable (); }/*
Signature · 1 line
1.5.5.1