chiark / gitweb /
b366e23a4bb3fe8d530bed5be6ed6bf826e33187
[chiark-utils.git] / fishdescriptor / greenend / fishdescriptor / indonor.py
1
2 # class for use inside gdb which is debugging the donor process
3
4 import gdb
5 import os
6
7 try:
8     rtld_now = os.RTLD_NOW
9 except AttributeError:
10     try:
11         import dl
12         rtld_now = dl.RTLD_NOW
13     except ImportError:
14         # some installations lack dl, it seems
15         # https://bugs.launchpad.net/ubuntu/+source/python2.7/+bug/1721840
16         # bodge:
17         rtld_now = 2
18
19 def _string_escape_for_c(s):
20     if not isinstance(s, bytes):
21         s = s.encode('utf-8') # sigh, python 2/3 compat
22     out = ''
23     for c in bytearray(s): # gets us bytes in py2 and py3
24         if c == ord('\\') or c == ord('=') or c < 32 or c > 126:
25             out += '\\x%02x' % c
26         else:
27             out += chr(c)
28     return out
29
30 class DonorImplementation(preloaded=False):
31     def __init__(self):
32         # works on the current gdb.Inferior
33         # ideally should be reused if the same process is targetd
34         self._open = None
35         self._sym = None
36         if preloaded:
37             self._sym = 'fishdescriptor_donate'
38
39     def _func(self, functype, funcname, realargs):
40         expr = '((%s) %s) %s' % (functype, funcname, realargs)
41         return gdb.parse_and_eval(expr)
42
43     def _dlfunc(self, functype, funcname, realargs):
44         r = self._func(functype,funcname,realargs)
45         if not r:
46             err = self._func('char* (*)(void)', 'dlerror', '()')
47             if not err:
48                 err = 'dlerror said NULL!'
49             else:
50                 err = err.string()
51             raise RuntimeError("%s failed: %s" % (funcname, err))
52         return r
53
54     def _dlopen(self):
55         if self._open is not None: return
56         o = self._dlfunc('void* (*)(const char*, int)',
57                          'dlopen',
58                          '("libfishdescriptor-donate.so.1.0", %s)' % rtld_now)
59         self._open = '((void*)%s)' % o
60
61     def _dlsym(self):
62         if self._sym is not None: return
63         self._dlopen()
64         self._sym = self._dlfunc('void* (*)(void*, const char*)'
65                                  'dlsym',
66                                  '(%s, "fishdescriptor_donate")' % self._open)
67
68     def donate(self, path, fds):
69         self._dlsym()
70         r = self._func('int (*)(const char*, const int*)',
71                        self._sym,
72                        '("%s", (int[%d]){ %s, -1 })'
73                        % (_string_escape_for_c(path),
74                           len(fds) + 1,
75                           ', '.join(["%d" % fd for fd in fds])))
76         if r:
77             err = self._func('char* (*)(int)',
78                              strerror,
79                              r)
80             if not err:
81                 err = 'strerror failed'
82             else:
83                 err = err.string()
84             raise RuntimeError("donate failed: %s" % err)