[PATCH v2] Fix get ERESTARTSYS with m32 in x86_64 when debug by GDB

Hui Zhu teawater@gmail.com
Thu Jun 12 08:41:00 GMT 2014


Ping.

Thanks,
Hui

On Mon, May 5, 2014 at 8:00 PM, Hui Zhu <hui@codesourcery.com> wrote:
> cat gdb.base/interrupt.c
> #include <errno.h>
> #include <stdio.h>
> #include <unistd.h>
> #include <stdlib.h>
>
> #ifdef SIGNALS
> #include <signal.h>
>
> static void
> sigint_handler (int signo)
> {
> }
> #endif
>
> int
> main ()
> {
>   char x;
>   int nbytes;
> #ifdef SIGNALS
>   signal (SIGINT, sigint_handler);
> #endif
>   printf ("talk to me baby\n");
>   while (1)
>     {
>       nbytes = read (0, &x, 1);
>       if (nbytes < 0)
> {
> #ifdef EINTR
>  if (errno != EINTR)
> #endif
>    {
>      perror ("");
>      return 1;
>    }
> }
>       else if (nbytes == 0)
> {
>  printf ("end of file\n");
>  exit (0);
> }
>       else
> write (1, &x, 1);
>     }
>   return 0;
> }
>
> int
> func1 ()
> {
>   return 4;
> }
> gcc -g -m32 gdb.base/interrupt.c
> gdb ./a.out
> (gdb) r
> Starting program: /home/teawater/gdb/binutils-gdb/gdb/testsuite/a.out
> talk to me baby
> data
> data
> ^C
> Program received signal SIGINT, Interrupt.
> 0xf7ffd430 in __kernel_vsyscall ()
> (gdb) c
> Continuing.
> ^C
> Program received signal SIGINT, Interrupt.
> 0xf7ffd430 in __kernel_vsyscall ()
> (gdb) p func1()
> $1 = 4
> (gdb) c
> Continuing.
> Unknown error 512
> [Inferior 1 (process 7953) exited with code 01]
>
> The root cause is:
> When inferior call 32 bits syscall "read", Linux kernel function
> "ia32_cstar_target" will set TS_COMPAT to current_thread_info->status.
>
> syscall read is interrupt by ctrl-c.   Then the $rax will be set to
> errno -512 in 64 bits.
> And the inferior will be stopped by Linux kernel function ptrace_stop,
> the call trace is:
> #0  freezable_schedule () at include/linux/freezer.h:172
> #1  ptrace_stop (exit_code=exit_code@entry=5, why=why@entry=262148,
>     clear_code=clear_code@entry=0, info=info@entry=0xffff88001d833e78)
>     at kernel/signal.c:1920
> #2  0xffffffff8107ec33 in ptrace_signal (info=0xffff88001d833e78, signr=5)
>     at kernel/signal.c:2157
> #3  get_signal_to_deliver (info=info@entry=0xffff88001d833e78,
>     return_ka=return_ka@entry=0xffff88001d833e58, regs=<optimized out>,
>     cookie=cookie@entry=0x0 <irq_stack_union>) at kernel/signal.c:2269
> #4  0xffffffff81013438 in do_signal (regs=regs@entry=0xffff88001d833f58)
>     at arch/x86/kernel/signal.c:696
> #5  0xffffffff81013a40 in do_notify_resume (regs=0xffff88001d833f58,
>     unused=<optimized out>, thread_info_flags=4) at arch/x86/kernel/signal.c:747
> #6  <signal handler called>
> #7  0x0000000000000000 in irq_stack_union ()
>
> After that, GDB can control the stopped inferior.
> To call function "func1()" of inferior, GDB need:
> Step 1, save current values of registers ($rax 0xfffffffffffffe00(64 bits -512)
> is cut to 0xfffffe00(32 bits -512) because inferior is a 32 bits program).
> Step 2, change the values of registers.
> Step 3, Push a dummy frame to stack.
> Step 4, set a breakpint in the return address.
>
> When GDB resume the inferior, it will keep execut from ptrace_stop
> with new values of registers that set by GDB.
> And TS_COMPAT inside current_thread_info->status will be cleared when
> inferior switch back to user space.
>
> When function "func1()" return, inferior will be stoped by breakpoint
> inferior will be stopped by Linux kernel function "ptrace_stop" again.
> current_thread_info->status will not set TS_COMPAT when inferior swith
> from user space to kernel space because breakpoint handler "int3" doesn't
> has code for that.
>
> GDB begin to set saved values of registers back to inferior that use
> function "amd64_collect_native_gregset".  Because this function just
> zero-extend each 32 bits value to 64 bits value before put them to inferior.
> $rax's value is set to 0xfffffe00(32 bits -512) but not
> 0xfffffffffffffe00(64 bits -512).
>
> When GDB continue syscall "read" that is interrupted by "ctrl-c", it will
> keep execute from ptrace_stop without "TS_COMPAT".
> Then in Linux kernel function "syscall_get_error", current_thread_info->status
> doesn't have TS_COMPAT and $rax is 0xfffffe00(32 bits -512).  Then in
> function do_signal will not handle this -ERESTARTSYS.
>
> -ERESTARTSYS will be return back to inferior, that is why inferior got a
> errno -ERESTARTSYS.
>
> According to comments from Mark.  I make a new version that add following
> code to putreg():
>         case offsetof(struct user_regs_struct, orig_ax):
>                 /*
>                  * A 64-bit debugger setting orig_ax of a 32-bit inferior
>                  * means to restore the state of the task restarting a
>                  * 32-bit syscall.
>                  * Make sure we interpret the -ERESTART* codes correctly
>                  * in case the task is not actually still sitting at the
>                  * exit from a 32-bit syscall with TS_COMPAT still set.
>                  */
>                 if (test_ti_thread_flag(task_thread_info(child), TIF_IA32)) {
>                         struct pt_regs *regs = task_pt_regs(child);
>                         regs->orig_ax = value;
>                         if (syscall_get_nr(child, regs) >= 0)
>                                 task_thread_info(child)->status |= TS_COMPAT;
>                         return 0;
>                 }
>                 break;
>
> Signed-off-by: Hui Zhu <hui@codesourcery.com>
> ---
> --- a/arch/x86/kernel/ptrace.c
> +++ b/arch/x86/kernel/ptrace.c
> @@ -452,6 +452,23 @@ static int putreg(struct task_struct *ch
>                 if (child->thread.gs != value)
>                         return do_arch_prctl(child, ARCH_SET_GS, value);
>                 return 0;
> +       case offsetof(struct user_regs_struct, orig_ax):
> +               /*
> +                * A 64-bit debugger setting orig_ax of a 32-bit inferior
> +                * means to restore the state of the task restarting a
> +                * 32-bit syscall.
> +                * Make sure we interpret the -ERESTART* codes correctly
> +                * in case the task is not actually still sitting at the
> +                * exit from a 32-bit syscall with TS_COMPAT still set.
> +                */
> +               if (test_ti_thread_flag(task_thread_info(child), TIF_IA32)) {
> +                       struct pt_regs *regs = task_pt_regs(child);
> +                       regs->orig_ax = value;
> +                       if (syscall_get_nr(child, regs) >= 0)
> +                               task_thread_info(child)->status |= TS_COMPAT;
> +                       return 0;
> +               }
> +               break;
>  #endif
>         }
>



More information about the Gdb mailing list