2

Why gdb doesn't break when I use exec-wrapper script to exec my target binary.

Here my little test.

I use a binary with no pie option to use predictable address.

$ gcc -no-pie main.c

I have this litle gdb exec wrapper script.

$ cat wrapper.sh 
#!/bin/bash

env -i ./a.out

Then I debug my program like this, by setting the exec-wrapper variable and a debug breakpoint at *main, the breakpoint doesn't work:

$ gdb -q ./a.out 
Reading symbols from ./a.out...(no debugging symbols found)...done.
(gdb) b *main
Breakpoint 1 at 0x401126
(gdb) set exec-wrapper ./wrapper.sh
(gdb) r
Starting program: /home/snake/gdb-test/a.out 
boum !
During startup program exited normally.
(gdb)

Here the same example without exec-wrapper variable set and a debug breakpoint at *main, the breakpoint works:

$ gdb -q ./a.out 
Reading symbols from ./a.out...(no debugging symbols found)...done.
(gdb) b *main
Breakpoint 1 at 0x401126
(gdb) r
Starting program: /home/snake/gdb-test/a.out 

Breakpoint 1, 0x0000000000401126 in main ()
(gdb) 

Why in the first case the breakpoint not work?

Maybe gdb is not attached to the process run by my exec wrapper script.

Thank's for your help.

UPDATE:

Thank's @ws, your reminder and point of view is interesting.

I use env -i in order to start the program in an empty environment in order to have a predictable stack in a non randomized address space, in/out of gdb and in any path. But GDB lack of options to configure the binary name with a relative path to pass to argv[0] and other variables pushed on the stack will cause a stack offset according the directory location the execution context.

So I have build this little wrapper to overcome the problem. Maybe dirty, but work well.

#include <unistd.h>

int main()
{
  int e;
  char *envp[] = { NULL };
  char *argv[] = { "./a.out", NULL };

  e = execve("./a.out", argv, envp);
}

then

$ gcc exec_wrapper.c -o exec_wrapper

and finaly

$ gdb -q ./a.out 
Reading symbols from ./a.out...(no debugging symbols found)...done.
(gdb) set exec-wrapper ./exec_wrapper
(gdb) b *main
Breakpoint 1 at 0x1149
(gdb) r
Starting program: /home/snake/gdb-test/a.out 

Breakpoint 1, 0x0000555555555149 in main ()
(gdb)

So I don't understand your answer very well @ws, my executable name is directly encoded in the binary, and it works well. If I understand the behavior of gdb, normally GDB monitors for the first process trap execve() start by the shell to follow it. In the case of exec-wrapper is configure, it follow the second execve(). (thank's to a friend for show me this behavior).

gdb/nat/fork-inferior.h

/* Number of traps that happen between exec'ing the shell to run an
   inferior and when we finally get to the inferior code, not counting
   the exec for the shell.  This is 1 on all supported
   implementations.  */
#define START_INFERIOR_TRAPS_EXPECTED 1

gdb/nat/fork-inferior.c

  /* The process was started by the fork that created it, but it will
     have stopped one instruction after execing the shell.  Here we
     must get it up to actual execution of the real program.  */
  if (get_exec_wrapper () != NULL)
       pending_execs++;

Maybe I doesn't understand your answer @ws. sorry. you meant to say that gdb performs string comparison of the binary name with full path with the argv[0] of the execve() call?

3
  • Why not "set exec-wrapper env -i " ? It looks like that with a wrapper bash script your wrapper appears to be bash (because of shebang) - and it doesn't load the executable directly, which doesn't fit to the definition of gdb execution wrapper and may lead to the specified behavior.
    – w s
    Commented Oct 1, 2018 at 15:13
  • Yes, with "set exec-wrapper env -i " it works well, nice. Maybe my use of exec-wrapper is not good. thank's. This behaviour is fixed for me. But I realize, is not the good answer for what I search. I will close this thread and open an other. by
    – user23611
    Commented Oct 1, 2018 at 15:33
  • @ws it might still be interesting to explain the difference in the behavior here. Can you add an answer with the explanation? (maybe also mention what ‘env -i’ does)
    – Igor Skochinsky
    Commented Oct 2, 2018 at 21:12

1 Answer 1

4

In short, it is bad exec-wrapper. Exec-wrapper is defined (for example, here) as

When ‘exec-wrapper’ is set, the specified wrapper is used to launch programs for debugging. GDB starts your program with a shell command of the form exec wrapper program. Quoting is added to program and its arguments, but not to wrapper, so you should add quotes if appropriate for your shell. The wrapper runs until it executes your program, and then GDB takes control.

You can use any program that eventually calls execve with its arguments as a wrapper. Several standard Unix utilities do this, e.g. env and nohup. Any Unix shell script ending with exec "$@" will also work.

For example, you can use env to pass an environment variable to the debugged program, without setting the variable in your shell’s environment:

(gdb) set exec-wrapper env 'LD_PRELOAD=libtest.so' (gdb) run This command is available when debugging locally on most targets, excluding DJGPP, Cygwin, MS Windows, and QNX Neutrino.

As far as I can tell, your shell script doesn't fit

Any Unix shell script ending with exec "$@" will also work.

and therefore will not work.

Some explanations:

For the start, some definitions.

  • gdb is a debugger, we all know what it does mean. It is written on a base of ptrace system call, which allows (among other functionality) to debug and intercept system calls of the "child process"
  • child process is a process created by the parent process by usage of fork or clone and execve system call, which can be intercepted by ptrace.
  • exec-wrapper is an execution wrapper in gdb (see the quote before). It is needed for customizing environment of the execution, see the documentation for it here.
  • env - is a program for modifying the execution environment, for example from environment variables point of view (by the way, env -i means no environment variables at all)

If gdb intercepts the execve system call of the debugged process with ptrace (as far as I understand it should do it this way), it can not find your executable name in the system call parameters (because it is directly encoded into your script) and therefore not performs setting the breakpoint.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.