This is the mail archive of the gdb@sourceware.org mailing list for the GDB project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

Re: GDB and LD_PRELOAD library-call interception


On Thu, Mar 31, 2011 at 2:16 PM, Jan Kratochvil wrote:
>
> It is not such straightforward, GDB expects the PID it has spawned will be
> debugged while with xterm the process being debugged is its child.
>
> I guess you can write a small C helper which will:
>  * create new pty
>  * change its fds 0/1/2 to the slave of this pty
>  * fork xterm -e own-helper-part pty-unique-id
> In own-helper-part interconnect the pty master part and its fds 0/1/2.
>


The attached files 'xterm_wrapper.py' and 'interconnect_pty.py' are a
raw implementation written in python of the above scheme. In gdb do:

    set exec-wrapper python xterm_wrapper.py

The problem with the implementation is that the debuggee cannot set
the slave pty as its controlling terminal without forking (set
SET_CONTROLLING_TERMINAL to True in 'xterm_wrapper.py' in order to do
that, but then exec-wrapper cannot be used). So that it is not
possible to interrupt the debuggee with a 'C-c' character.

On the other hand the 'interconnect_pty.py' helper used by
'xterm_wrapper.py' can also be used by itself in stand-alone. When run
without arguments, it creates a pty and prints the slave pty name so
that it can be used in gdb with:

    set inferior-tty /dev/pts/nn

It can also be made to spawn gdb in a new xterm and set correctly the
'-tty' gdb option with tne new pty name (kind of the reverse of the
initial scheme):

    python interconnect_pty.py --exec gdb --args '/path/to/debuggee'

Xavier
#! /usr/bin/env python

import os
import sys
import signal
import errno
import copy
import pty
import optparse
import termios
import asyncore

class FileDispatcher(asyncore.file_dispatcher):
    def __init__(self, in_fd, out_fd, quit_char=None):
        asyncore.file_dispatcher.__init__(self, in_fd)
        self.out_fd = out_fd
        self.quit_char = quit_char

    def writable(self):
        return False

    def handle_read(self):
        # called from the select loop whenever data is available
        try:
            data = self.recv(1024)
            # terminate the select loop
            if data == self.quit_char:
                raise asyncore.ExitNow(
                        '\n%s terminated.' % os.path.basename(sys.argv[0]))
            os.write(self.out_fd, data)
        except OSError, err:
            if err[0] != errno.EAGAIN:
                raise asyncore.ExitNow(err)

def parse_options(argv):
    formatter = optparse.IndentedHelpFormatter(max_help_position=30)
    parser = optparse.OptionParser(
                    usage='usage: python %prog [options]',
                    formatter=formatter)
    parser.add_option('-e', '--exec',
            type='string', dest='pgm',
            help='program to spawn in a new xterm terminal '
            'that will use the slave pseudo terminal')
    parser.add_option('-t', '--tty',
            type='string', default='-tty', metavar='TTY_OPT',
            help='pass to PGM the slave pseudo terminal name using'
            ' the TTY_OPT option (default \'%default\')')
    parser.add_option('-a', '--args',
            type='string',
            help='PGM arguments (must be quoted)')
    parser.add_option('-m', '--master_fd',
            type='int', metavar='FD',
            help='master pseudo terminal file descriptor to use')
    (options, args) = parser.parse_args(args=argv)
    return options

def spawn_xterm(options, ptyname, slave_fd, master_fd):
    argv = ['xterm', '-e', options.pgm, options.tty, ptyname]
    if options.args is not None:
        # FIXME quotes in args are not handled
        argv.extend(options.args.split())
    pid = os.fork()
    if pid == 0:
        os.close(slave_fd)
        os.close(master_fd)
        os.setsid()
        os.execvp(argv[0], argv)

master_fd = None

def sigint_handler(signum, frame):
    # write a C-c character
    if master_fd is not None:
        os.write(master_fd, chr(3))

def main():
    global master_fd
    options = parse_options(sys.argv[1:])
    master_fd = options.master_fd
    if not isinstance(master_fd, int):
        master_fd, slave_fd = pty.openpty()
        ptyname = os.ttyname(slave_fd)
        print "Slave pseudo terminal to use: '%s'" % ptyname
        if options.pgm is not None:
            spawn_xterm(options, ptyname, slave_fd, master_fd)
    print 'Type C-a to exit.\n'

    signal.signal(signal.SIGINT, sigint_handler)

    # no echo, no canonical processing
    attr = termios.tcgetattr(0)
    init_attr = copy.deepcopy(attr)
    attr[3] = attr[3] & ~termios.ECHO
    attr[3] = attr[3] & ~termios.ICANON
    termios.tcsetattr(0, termios.TCSADRAIN, attr)

    # interconnect stdin to master_fd, and master_fd to stdout
    # with an asyncore select loop
    # C-a is the Quit character
    master_slave = FileDispatcher(0, master_fd, chr(1))
    slave_master = FileDispatcher(master_fd, 1)

    err = ''
    try:
        while asyncore.socket_map:
            asyncore.poll()
    except asyncore.ExitNow, err:
        pass

    os.close(slave_fd)
    os.close(master_fd)
    termios.tcsetattr(0, termios.TCSADRAIN, init_attr)
    if err:
        print err

if __name__ == '__main__':
        main()

#! /usr/bin/env python

import sys
import os
import pty
import termios
import fcntl

def fork_xterm(master_fd, slave_fd):
    pid = os.fork()
    if pid == 0:
        os.close(slave_fd)
        helper = os.path.join(
                    os.path.dirname(sys.argv[0]), 'interconnect_pty.py')
        argv = ['xterm', '-e', 'python', helper, '--master_fd', str(master_fd)]
        os.execvp(argv[0], argv)

    os.close(master_fd)
    return pid

def exec_pgm(slave_fd, argv):
    os.dup2(slave_fd, 0)
    os.dup2(slave_fd, 1)
    os.dup2(slave_fd, 2)
    os.close(slave_fd)
    os.execvp(argv[0], argv)

SET_CONTROLLING_TERMINAL = False

def main():
    master_fd, slave_fd = pty.openpty()
    pid = fork_xterm(master_fd, slave_fd)
    argv = sys.argv[1:]
    if SET_CONTROLLING_TERMINAL:
        argv[0] = os.path.abspath(argv[0])
        pid = os.fork()
        if pid == 0:
            os.setsid()
            fcntl.ioctl(slave_fd, termios.TIOCSCTTY)
            exec_pgm(slave_fd, argv)
    else:
        exec_pgm(slave_fd, argv)

if __name__ == '__main__':
    main()


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]