# class for use inside gdb which is debugging the donor process
import gdb
+import copy
import os
-try:
- rtld_now = os.RTLD_NOW
-except AttributeError:
- try:
- import dl
- rtld_now = dl.RTLD_NOW
- except ImportError:
- # some installations lack dl, it seems
- # https://bugs.launchpad.net/ubuntu/+source/python2.7/+bug/1721840
- # bodge:
- rtld_now = 2
-
def _string_escape_for_c(s):
- if not isinstance(s, bytes):
- s = s.encode('utf-8') # sigh, python 2/3 compat
out = ''
for c in bytearray(s): # gets us bytes in py2 and py3
if c == ord('\\') or c == ord('=') or c < 32 or c > 126:
out += chr(c)
return out
+# constructing values
+
+def _lit_integer(v):
+ return '%d' % v
+
+def _lit_aggregate_uncasted(val_lit_strs):
+ return '{' + ', '.join(['(%s)' % v for v in val_lit_strs]) + ' }'
+
+def _lit_string_uncasted(s):
+ if not isinstance(s, bytes):
+ s = s.encode('utf-8') # sigh, python 2/3 compat
+ b = bytearray(s)
+ return _lit_aggregate_uncasted(map(_lit_integer, b) + [ '0' ])
+
+def _lit_array(elemtype, val_lit_strs):
+ return (
+ '((%s[%d])%s)' %
+ (elem_type, len(val_lit_strs), _lit_aggregate_uncasted(val_lit_strs))
+ )
+
+def _lit_addressof(v):
+ return '&(%s)' % v
+
+def _make_lit(v):
+ if isinstance(v, int):
+ return _lit_integer(v)
+ else:
+ return v # should already be an integer
+
+class DonorStructLayout():
+ def __init__(l, typename):
+ x = gdb.parse_and_eval('(%s){ }' % typename)
+ l._typename = typename
+ l._template = [ ]
+ l._posns = { }
+ for f in x.type.fields():
+ l._posns[f.name] = len(l._template)
+ try f.type.fields(): blank = '{ }'
+ except AttributeError: blank = '0'
+ l._template.append(blank)
+ def substitute(l, values):
+ build = copy.deepcopy(l._template)
+ for (k,v) in values.items():
+ build[ l._posns[k] ] = _make_lit(v)
+ return '((%s)%s)' % (l._typename, _lit_aggregate_uncasted(build))
+
class DonorImplementation():
- def __init__(self, preloaded=False):
- # works on the current gdb.Inferior
- # ideally should be reused if the same process is targetd
- self._open = None
- self._sym = None
- if preloaded:
- self._sym = 'fishdescriptor_donate'
-
- def _func(self, functype, funcname, realargs):
+ def __init__(d):
+ d._structs = { }
+
+ # assembling structs
+ # sigh, we have to record the order of the arguments!
+ def d._find_fields(typename):
+ try:
+ fields = d._structs[typename]
+ except AttributeError:
+ fields = DonorStructLayout(typename)
+ d._structs[typename] = fields
+ return fields
+
+ def d._make(typename, values):
+ fields = d._find_fields(typename)
+ return fieldd.substitute(values)
+
+ # calling functions (need to cast the function name to the right
+ # type in case maybe gdb doesn't know the type)
+
+ def _func(d, functype, funcname, realargs):
expr = '((%s) %s) %s' % (functype, funcname, realargs)
return gdb.parse_and_eval(expr)
- def _dlfunc(self, functype, funcname, realargs):
- r = self._func(functype,funcname,realargs)
- if not r:
- err = self._func('char* (*)(void)', 'dlerror', '()')
- if not err:
- err = 'dlerror said NULL!'
- else:
- err = err.string()
- raise RuntimeError("%s failed: %s" % (funcname, err))
- return r
-
- def _dlopen(self):
- if self._open is not None: return
- o = self._dlfunc('void* (*)(const char*, int)',
- 'dlopen',
- '("libfishdescriptor-donate.so.1.0", %s)' % rtld_now)
- self._open = '((void*)%s)' % o
-
- def _dlsym(self):
- if self._sym is not None: return
- self._dlopen()
- self._sym = self._dlfunc('void* (*)(void*, const char*)'
- 'dlsym',
- '(%s, "fishdescriptor_donate")' % self._open)
-
- def donate(self, path, fds):
- self._dlsym()
- r = self._func('int (*)(const char*, const int*)',
- self._sym,
- '("%s", (int[%d]){ %s, -1 })'
- % (_string_escape_for_c(path),
- len(fds) + 1,
- ', '.join(["%d" % fd for fd in fds])))
- if r:
- err = self._func('char* (*)(int)',
- strerror,
- r)
- if not err:
- err = 'strerror failed'
- else:
- err = err.string()
- raise RuntimeError("donate failed: %s" % err)
+ def _must_func(d, functype, funcname, realargs):
+ retval = d._func(functype, funcname, realargs)
+ if retval < 0:
+ errnoval = gdb.parse_and_eval('errno')
+ raise RuntimeError("%s gave errno=%d `%s'" %
+ (funcname, errnoval, od.strerror(errnoval)))
+
+ # wrappers for the syscalls that do what we want
+
+ def _sendmsg(d, carrier, control_msg):
+ iov_base = _lit_array('int', map(str,fds))
+ iov = d._make('struct iovec', {
+ 'iov_base': iov_base,
+ 'iov_len' : len(fds),
+ })
+
+ msg = d._make('struct msghdr', {
+ 'msg_iov' : _lit_addressof(iov),
+ 'msg_iovlen' : 1,
+ 'msg_control' : _lit_array('char', control_msg),
+ 'msg_controllen': len(control_msg),
+ })
+
+ d._must_func(
+ 'ssize_t (*)(int, const struct msghdr*, int flags)',
+ 'sendmsg',
+ '(%s, %s, 0)' % (carrier, _lit_addressof(msg))
+ )
+
+ def _socket(d):
+ return d._must_func(
+ 'int (*)(int, int, int)',
+ 'socket',
+ '(%d, %d, 0)' % (socket.AF_UNIX, socket.SOCK_STREAM)
+ )
+
+ def _connect(d, fd, path):
+ addr = d._make('struct sockaddr_un', {
+ 'sun_family' : _lit_integer(socket.AF_UNIX),
+ 'sun_path' : _lit_string_uncasted(path),
+ })
+
+ d._must_func(
+ 'int (*)(int, const struct sockaddr*, socklen_t)',
+ 'connect',
+ '(%d, (const struct sockaddr*)%s, sizeof(struct sockaddr_un))'
+ % (fd, _lit_addressof(addr)
+ )
+
+ def _close(d, fd):
+ d._must_func('int (*)(int)', 'close', '(%d)' % fd)
+
+ # main entrypoints
+
+ def donate(d, path, control_msg):
+ # control_msg is an array of integers being the ancillary data
+ # array ("control") for sendmsg, and hence specifies which fds
+ # to pass
+
+ carrier = None
+ errnoval = None
+ try:
+ errnoval = gdb.parse_and_eval('errno')
+ carrier = d._socket()
+ d._connect(carrier, path)
+ d._sendmsg(carrier, control_msg)
+ d._close(carrier)
+ carrier = None
+ finally:
+ if carrier is not None:
+ try: d._close(carrier)
+ except Exception: pass
+ if errnoval is not None:
+ gdb.parse_and_eval('errno = %d' % errnoval)