blob: 123896179aeed7bf3244c261650a946f2e6701d7 [file] [log] [blame]
cmticeb70801a2014-12-11 14:29:34 -08001# Copyright 2014 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""Wrapper for running gdb.
6
7This handles the fun details like running against the right sysroot, via
8qemu, bind mounts, etc...
9"""
10
11from __future__ import print_function
12
13import argparse
14import contextlib
15import errno
16import os
17import sys
18import tempfile
19
20from chromite.lib import commandline
21from chromite.lib import cros_build_lib
22from chromite.lib import namespaces
23from chromite.lib import osutils
cmtice932e0aa2015-02-27 11:49:12 -080024from chromite.lib import qemu
cmticeb70801a2014-12-11 14:29:34 -080025from chromite.lib import retry_util
26
27GDB = '/usr/bin/gdb'
28
cmtice932e0aa2015-02-27 11:49:12 -080029
cmticeb70801a2014-12-11 14:29:34 -080030class BoardSpecificGdb(object):
31 """Framework for running gdb."""
32
33 _BIND_MOUNT_PATHS = ('dev', 'dev/pts', 'proc', 'mnt/host/source', 'sys')
34
cmtice932e0aa2015-02-27 11:49:12 -080035 def __init__(self, board, gdb_args, inf_cmd, inf_args):
cmticeb70801a2014-12-11 14:29:34 -080036 self.board = board
37 self.sysroot = cros_build_lib.GetSysroot(board=self.board)
38 self.prompt = '(%s-gdb) ' % self.board
39 self.host = False
40 self.run_as_root = False # May add an option to change this later.
cmtice932e0aa2015-02-27 11:49:12 -080041 self.gdb_args = gdb_args
42 self.inf_cmd = inf_cmd
43 self.inf_args = inf_args
44 self.framework = 'auto'
45 self.qemu = None
46
47 qemu_arch = qemu.Qemu.DetectArch(GDB, self.sysroot)
48 if qemu_arch is None:
49 self.framework = 'ldso'
50 else:
51 self.framework = 'qemu'
52 self.qemu = qemu.Qemu(self.sysroot, arch=qemu_arch)
cmticeb70801a2014-12-11 14:29:34 -080053
54 if not os.path.isdir(self.sysroot):
55 raise AssertionError('Sysroot does not exist: %s' % self.sysroot)
56
57 def removeSysrootPrefix(self, path):
58 """Returns the given path with any sysroot prefix removed."""
59 # If the sysroot is /, then the paths are already normalized.
60 if self.sysroot != '/' and path.startswith(self.sysroot):
61 path = path.replace(self.sysroot, '', 1)
62
63 return path
64
65 @staticmethod
66 def GetNonRootAccount():
67 """Return details about the non-root account we want to use.
68
69 Returns:
70 A tuple of (username, uid, gid, home).
71 """
72 return (
73 os.environ.get('SUDO_USER', 'nobody'),
74 int(os.environ.get('SUDO_UID', '65534')),
75 int(os.environ.get('SUDO_GID', '65534')),
76 # Should we find a better home?
77 '/tmp/portage',
78 )
79
80 @staticmethod
81 @contextlib.contextmanager
82 def LockDb(db):
83 """Lock an account database.
84
85 We use the same algorithm as shadow/user.eclass. This way we don't race
86 and corrupt things in parallel.
87 """
88 lock = '%s.lock' % db
89 _, tmplock = tempfile.mkstemp(prefix='%s.platform.' % lock)
90
91 # First try forever to grab the lock.
92 retry = lambda e: e.errno == errno.EEXIST
93 # Retry quickly at first, but slow down over time.
94 try:
95 retry_util.GenericRetry(retry, 60, os.link, tmplock, lock, sleep=0.1)
96 except Exception:
97 print('error: could not grab lock %s' % lock)
98 raise
99
100 # Yield while holding the lock, but try to clean it no matter what.
101 try:
102 os.unlink(tmplock)
103 yield lock
104 finally:
105 os.unlink(lock)
106
107 def SetupUser(self):
108 """Propogate the user name<->id mapping from outside the chroot.
109
110 Some unittests use getpwnam($USER), as does bash. If the account
111 is not registered in the sysroot, they get back errors.
112 """
113 MAGIC_GECOS = 'Added by your friendly platform test helper; do not modify'
114 # This is kept in sync with what sdk_lib/make_chroot.sh generates.
115 SDK_GECOS = 'ChromeOS Developer'
116
117 user, uid, gid, home = self.GetNonRootAccount()
118 if user == 'nobody':
119 return
120
121 passwd_db = os.path.join(self.sysroot, 'etc', 'passwd')
122 with self.LockDb(passwd_db):
123 data = osutils.ReadFile(passwd_db)
124 accts = data.splitlines()
125 for acct in accts:
126 passwd = acct.split(':')
127 if passwd[0] == user:
128 # Did the sdk make this account?
129 if passwd[4] == SDK_GECOS:
130 # Don't modify it (see below) since we didn't create it.
131 return
132
133 # Did we make this account?
134 if passwd[4] != MAGIC_GECOS:
135 raise RuntimeError('your passwd db (%s) has unmanaged acct %s' %
136 (passwd_db, user))
137
138 # Maybe we should see if it needs to be updated? Like if they
139 # changed UIDs? But we don't really check that elsewhere ...
140 return
141
142 acct = '%(name)s:x:%(uid)s:%(gid)s:%(gecos)s:%(homedir)s:%(shell)s' % {
143 'name': user,
144 'uid': uid,
145 'gid': gid,
146 'gecos': MAGIC_GECOS,
147 'homedir': home,
148 'shell': '/bin/bash',
149 }
150 with open(passwd_db, 'a') as f:
151 if data[-1] != '\n':
152 f.write('\n')
153 f.write('%s\n' % acct)
154
155 def run(self):
156 """Runs the debugger in a proper environment (e.g. qemu)."""
157 self.SetupUser()
158
cmtice932e0aa2015-02-27 11:49:12 -0800159 if self.framework == 'qemu':
160 self.qemu.Install(self.sysroot)
161 self.qemu.RegisterBinfmt()
162
cmticeb70801a2014-12-11 14:29:34 -0800163 for mount in self._BIND_MOUNT_PATHS:
164 path = os.path.join(self.sysroot, mount)
165 osutils.SafeMakedirs(path)
166 osutils.Mount('/' + mount, path, 'none', osutils.MS_BIND)
167
cmtice932e0aa2015-02-27 11:49:12 -0800168 gdb_cmd = GDB
169 inferior_cmd = self.removeSysrootPrefix(self.inf_cmd)
170 gdb_argv = self.gdb_args[:]
171 if gdb_argv:
172 gdb_argv[0] = self.removeSysrootPrefix(gdb_argv[0])
cmticeb70801a2014-12-11 14:29:34 -0800173
174 # Some programs expect to find data files via $CWD, so doing a chroot
175 # and dropping them into / would make them fail.
176 cwd = self.removeSysrootPrefix(os.getcwd())
177
178 print('chroot: %s' % self.sysroot)
179 print('cwd: %s' % cwd)
cmtice932e0aa2015-02-27 11:49:12 -0800180 if gdb_argv:
181 print('cmd: {%s} %s' % (gdb_cmd, ' '.join(map(repr, gdb_argv))))
cmticeb70801a2014-12-11 14:29:34 -0800182 os.chroot(self.sysroot)
183 os.chdir(cwd)
184 # The TERM the user is leveraging might not exist in the sysroot.
185 # Force a sane default that supports standard color sequences.
186 os.environ['TERM'] = 'ansi'
187 # Some progs want this like bash else they get super confused.
188 os.environ['PWD'] = cwd
189 if not self.run_as_root:
190 _, uid, gid, home = self.GetNonRootAccount()
191 os.setgid(gid)
192 os.setuid(uid)
193 os.environ['HOME'] = home
194
cmtice932e0aa2015-02-27 11:49:12 -0800195 gdb_commands = [
cmticeb70801a2014-12-11 14:29:34 -0800196 'set sysroot /',
197 'set solib-absolute-prefix /',
198 'set solib-search-path /',
199 'set debug-file-directory /usr/lib/debug',
200 'set prompt %s' % self.prompt
cmtice932e0aa2015-02-27 11:49:12 -0800201 ]
cmticeb70801a2014-12-11 14:29:34 -0800202
cmtice932e0aa2015-02-27 11:49:12 -0800203 if self.inf_args:
204 arg_str = self.inf_args[0]
205 for arg in self.inf_args[1:]:
206 arg_str += ' %s' % arg
207 gdb_commands.append('set args %s' % arg_str)
208
209 print ("gdb_commands: %s" % repr(gdb_commands))
210
211 gdb_args = [gdb_cmd] + ['--eval-command=%s' % x for x in gdb_commands]
212 gdb_args += self.gdb_args
213
214 if inferior_cmd:
215 gdb_args.append(inferior_cmd)
216
217 print ("args: %s" % repr(gdb_args))
218 sys.exit(os.execvp(gdb_cmd, gdb_args))
cmticeb70801a2014-12-11 14:29:34 -0800219
220
cmtice932e0aa2015-02-27 11:49:12 -0800221def _ReExecuteIfNeeded(argv, ns_net=False, ns_pid=False):
cmticeb70801a2014-12-11 14:29:34 -0800222 """Re-execute gdb as root.
223
224 We often need to do things as root, so make sure we're that. Like chroot
225 for proper library environment or do bind mounts.
226
227 Also unshare the mount namespace so as to ensure that doing bind mounts for
228 tests don't leak out to the normal chroot. Also unshare the UTS namespace
229 so changes to `hostname` do not impact the host.
230 """
231 if os.geteuid() != 0:
232 cmd = ['sudo', '-E', '--'] + argv
233 os.execvp(cmd[0], cmd)
234 else:
cmtice932e0aa2015-02-27 11:49:12 -0800235 namespaces.SimpleUnshare(net=ns_net, pid=ns_pid)
236
237
238def find_inferior(arg_list):
239 """Look for the name of the inferior (to be debugged) in arg list."""
240
241 program_name = ''
242 new_list = []
243 for item in arg_list:
244 if item[0] == '-':
245 new_list.append(item)
246 elif not program_name:
247 program_name = item
248 else:
249 raise RuntimeError('Found multiple program names: %s %s'
250 % (program_name, item))
251
252 return program_name, new_list
cmticeb70801a2014-12-11 14:29:34 -0800253
254
255def main(argv):
256
257 parser = commandline.ArgumentParser(description=__doc__)
258
259 parser.add_argument('--board', required=True,
260 help='board to debug for')
cmtice932e0aa2015-02-27 11:49:12 -0800261 parser.add_argument('--set_args', dest='set_args', default='',
262 help='Arguments for gdb to pass through to the executable'
263 ' file.')
264 parser.add_argument('gdb_args', nargs=argparse.REMAINDER,
265 help='Arguments to gdb itself. Must come at end of'
266 ' command line.')
cmticeb70801a2014-12-11 14:29:34 -0800267
268 options = parser.parse_args(argv)
269 options.Freeze()
270
cmtice932e0aa2015-02-27 11:49:12 -0800271 gdb_args = []
272 inf_args = []
273 inf_cmd = ''
274
275 if options.gdb_args:
276 inf_cmd, gdb_args = find_inferior(options.gdb_args)
277
278 if options.set_args:
279 inf_args = options.set_args.split()
280
281 if inf_cmd:
282 fname = os.path.join(cros_build_lib.GetSysroot(options.board),
283 inf_cmd.lstrip('/'))
284 if not os.path.exists(fname):
285 cros_build_lib.Die('Cannot find program %s.' % fname)
286
287 if inf_args and not inf_cmd:
288 cros_build_lib.Die('Cannot specify arguments without a program.')
289
cmticeb70801a2014-12-11 14:29:34 -0800290 # Once we've finished sanity checking args, make sure we're root.
291 _ReExecuteIfNeeded([sys.argv[0]] + argv)
292
cmtice932e0aa2015-02-27 11:49:12 -0800293 gdb = BoardSpecificGdb(options.board, gdb_args, inf_cmd, inf_args)
cmticeb70801a2014-12-11 14:29:34 -0800294
295 gdb.run()