2 # This file is part of chiark-utils, a collection of useful programs
3 # used on chiark.greenend.org.uk.
6 # Copyright 2018 Citrix Systems Ltd
8 # This is free software; you can redistribute it and/or modify it under the
9 # terms of the GNU General Public License as published by the Free Software
10 # Foundation; either version 3, or (at your option) any later version.
12 # This is distributed in the hope that it will be useful, but WITHOUT ANY
13 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
14 # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
17 # You should have received a copy of the GNU General Public License along
18 # with this program; if not, consult the Free Software Foundation's
19 # website at www.fsf.org, or the GNU Project website at www.gnu.org.
21 # class for use inside gdb which is debugging the donor process
23 from __future__ import print_function
31 def _string_bytearray(s):
32 # gets us bytes in py2 and py3
33 if not isinstance(s, bytes):
34 s = s.encode('utf-8') # sigh, python 2/3 compat
37 def _string_escape_for_c(s):
39 for c in _string_bytearray(s):
40 if c == ord('\\') or c == ord('"') or c < 32 or c > 126:
51 def _lit_aggregate_uncasted(val_lit_strs):
52 return '{' + ', '.join(['(%s)' % v for v in val_lit_strs]) + ' }'
54 def _lit_string_uncasted(s):
55 b = _string_bytearray(s)
56 return _lit_aggregate_uncasted([_lit_integer(x) for x in b] + [ '0' ])
58 def _lit_array(elemtype, val_lit_strs):
61 (elemtype, len(val_lit_strs), _lit_aggregate_uncasted(val_lit_strs))
64 def _lit_addressof(v):
65 return '&(char[])(%s)' % v
68 if isinstance(v, int):
69 return _lit_integer(v)
71 return v # should already be an integer
74 sys.stderr.write("## EVAL %s\n" % repr(expr))
75 x = gdb.parse_and_eval(expr)
76 sys.stderr.write('## => %s\n' % x)
80 class DonorStructLayout():
81 def __init__(l, typename):
82 x = gdb.lookup_type(typename)
83 l._typename = typename
87 l._posns[f.name] = len(l._template)
88 try: f.type.fields(); blank = '{ }'
89 except TypeError: blank = '0'
90 except AttributeError: blank = '0'
91 l._template.append(blank)
92 sys.stderr.write('## STRUCT %s template %s fields %s\n'
93 % (typename, l._template, l._posns))
95 def substitute(l, values):
96 build = copy.deepcopy(l._template)
97 for (k,v) in values.items():
98 build[ l._posns[k] ] = _make_lit(v)
99 return '((%s)%s)' % (l._typename, _lit_aggregate_uncasted(build))
101 class DonorImplementation():
104 di._saved_errno = None
105 di._result_stream = os.fdopen(3, 'w')
106 di._errno_workaround = None
109 # sigh, we have to record the order of the arguments!
110 def _find_fields(di, typename):
112 fields = di._structs[typename]
114 fields = DonorStructLayout(typename)
115 di._structs[typename] = fields
118 def _make(di, typename, values):
119 fields = di._find_fields(typename)
120 return fields.substitute(values)
124 def _parse_eval_errno(di, expr_pat):
125 # evaluates expr_pat % 'errno'
126 if di._errno_workaround is not True:
128 x = parse_eval(expr_pat % 'errno')
129 di._errno_workaround = False
131 except gdb.error as e:
132 if di._errno_workaround is False:
134 di._errno_workaround = True
135 # Incomprehensibly, gdb.parse_and_eval('errno') can sometimes
137 # gdb.error: Cannot find thread-local variables on this target
138 # even though plain gdb `print errno' works while `print errno = 25'
139 # doesn't. OMG. This may be related to:
140 # https://github.com/cloudburst/libheap/issues/24
141 # although I can't find it in the gdb bug db (which is half-broken
142 # in my browser). Also the error is very nonspecific :-/.
143 # This seems to happen on jessie, and is fixed in stretch.
145 return parse_eval(expr_pat % '(*((int*(*)(void))__errno_location)())')
147 # calling functions (need to cast the function name to the right
148 # type in case maybe gdb doesn't know the type)
150 def _func(di, functype, funcname, realargs):
151 expr = '((%s) %s) %s' % (functype, funcname, realargs)
152 return parse_eval(expr)
154 def _must_func(di, functype, funcname, realargs):
155 retval = di._func(functype, funcname, realargs)
157 errnoval = di._parse_eval_errno('%s')
158 raise RuntimeError("%s gave errno=%d `%s'" %
159 (funcname, errnoval, os.strerror(errnoval)))
162 # wrappers for the syscalls that do what we want
164 def _sendmsg(di, carrier, control_msg):
165 iov_base = _lit_array('char', [1])
166 iov = di._make('struct iovec', {
167 'iov_base': iov_base,
171 msg = di._make('struct msghdr', {
172 'msg_iov' : _lit_addressof(iov),
174 'msg_control' : _lit_array('char', control_msg),
175 'msg_controllen': len(control_msg),
179 'ssize_t (*)(int, const struct msghdr*, int)',
181 '(%s, %s, 0)' % (carrier, _lit_addressof(msg))
185 return di._must_func(
186 'int (*)(int, int, int)',
188 '(%d, %d, 0)' % (socket.AF_UNIX, socket.SOCK_STREAM)
191 def _connect(di, fd, path):
192 addr = di._make('struct sockaddr_un', {
193 'sun_family' : _lit_integer(socket.AF_UNIX),
194 'sun_path' : _lit_string_uncasted(path),
198 'int (*)(int, const struct sockaddr*, socklen_t)',
200 '(%d, (const struct sockaddr*)%s, sizeof(struct sockaddr_un))'
201 % (fd, _lit_addressof(addr))
205 di._must_func('int (*)(int)', 'close', '(%d)' % fd)
207 def _mkdir(di, path, mode):
209 'int (*)(const char*, mode_t)',
211 '("%s", %d)' % (_string_escape_for_c(path), mode)
214 errnoval = di._parse_eval_errno('%s')
215 if errnoval != os.errno.EEXIST:
216 raise RuntimeError("mkdir %s failed: `%s'" %
217 (repr(path), os.strerror(errnoval)))
222 di._saved_errno = di._parse_eval_errno('%s')
224 def _errno_restore(di):
225 to_restore = di._saved_errno
226 di._saved_errno = None
227 if to_restore is not None:
228 di._parse_eval_errno('%%s = %d' % to_restore)
230 def _result(di, output):
231 sys.stderr.write("#> %s" % output)
232 di._result_stream.write(output)
233 di._result_stream.flush()
237 def donate(di, path, control_msg):
238 # control_msg is an array of integers being the ancillary data
239 # array ("control") for sendmsg, and hence specifies which fds
245 carrier = di._socket()
246 di._connect(carrier, path)
247 di._sendmsg(carrier, control_msg)
251 if carrier is not None:
252 try: di._close(carrier)
253 except Exception: pass
261 val = di._must_func('uid_t (*)(void)', 'geteuid', '()')
265 di._result('%d\n' % val)
270 val = di._mkdir(path, int('0700', 8))
274 di._result('%d\n' % val)
276 def _protocol_read(di):
277 input = sys.stdin.readline()
278 if input == '': return None
279 input = input.rstrip('\n')
280 sys.stderr.write("#< %s\n" % input)
284 if not gdb.selected_inferior().was_attached:
285 print('gdb inferior not attached', file=sys.stderr)
289 cmd = di._protocol_read()
290 if cmd is None: break