• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Gallopsled / pwntools / 3797606986

pending completion
3797606986

push

github-actions

GitHub
Merge branch 'dev' into rop_raw_list

3931 of 6599 branches covered (59.57%)

102 of 102 new or added lines in 15 files covered. (100.0%)

12074 of 16876 relevant lines covered (71.55%)

0.72 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

54.99
/pwnlib/gdb.py
1
# -*- coding: utf-8 -*-
2
"""
1✔
3
During exploit development, it is frequently useful to debug the
4
target binary under GDB.
5

6
Pwntools makes this easy-to-do with a handful of helper routines, designed
7
to make your exploit-debug-update cycles much faster.
8

9
Useful Functions
10
----------------
11

12
- :func:`attach` - Attach to an existing process
13
- :func:`debug` - Start a new process under a debugger, stopped at the first instruction
14
- :func:`debug_shellcode` - Build a binary with the provided shellcode, and start it under a debugger
15

16
Debugging Tips
17
--------------
18

19
The :func:`attach` and :func:`debug` functions will likely be your bread and
20
butter for debugging.
21

22
Both allow you to provide a script to pass to GDB when it is started, so that
23
it can automatically set your breakpoints.
24

25
Attaching to Processes
26
~~~~~~~~~~~~~~~~~~~~~~
27

28
To attach to an existing process, just use :func:`attach`.  It is surprisingly
29
versatile, and can attach to a :class:`.process` for simple
30
binaries, or will automatically find the correct process to attach to for a
31
forking server, if given a :class:`.remote` object.
32

33
Spawning New Processes
34
~~~~~~~~~~~~~~~~~~~~~~
35

36
Attaching to processes with :func:`attach` is useful, but the state the process
37
is in may vary.  If you need to attach to a process very early, and debug it from
38
the very first instruction (or even the start of ``main``), you instead should use
39
:func:`debug`.
40

41
When you use :func:`debug`, the return value is a :class:`.tube` object
42
that you interact with exactly like normal.
43

44
Using GDB Python API
45
~~~~~~~~~~~~~~~~~~~~
46

47
GDB provides Python API, which is documented at
48
https://sourceware.org/gdb/onlinedocs/gdb/Python-API.html. Pwntools allows you
49
to call it right from the exploit, without having to write a gdbscript. This is
50
useful for inspecting program state, e.g. asserting that leaked values are
51
correct, or that certain packets trigger a particular code path or put the heap
52
in a desired state.
53

54
Pass ``api=True`` to :func:`attach` or :func:`debug` in order to enable GDB
55
Python API access. Pwntools will then connect to GDB using RPyC library:
56
https://rpyc.readthedocs.io/en/latest/.
57

58
At the moment this is an experimental feature with the following limitations:
59

60
- Only Python 3 is supported.
61

62
  Well, technically that's not quite true. The real limitation is that your
63
  GDB's Python interpreter major version should be the same as that of
64
  Pwntools. However, most GDBs use Python 3 nowadays.
65

66
  Different minor versions are allowed as long as no incompatible values are
67
  sent in either direction. See
68
  https://rpyc.readthedocs.io/en/latest/install.html#cross-interpreter-compatibility
69
  for more information.
70

71
  Use
72

73
  ::
74

75
      $ gdb -batch -ex 'python import sys; print(sys.version)'
76

77
  in order to check your GDB's Python version.
78
- If your GDB uses a different Python interpreter than Pwntools (for example,
79
  because you run Pwntools out of a virtualenv), you should install ``rpyc``
80
  package into its ``sys.path``. Use
81

82
  ::
83

84
      $ gdb -batch -ex 'python import rpyc'
85

86
  in order to check whether this is necessary.
87
- Only local processes are supported.
88
- It is not possible to tell whether ``gdb.execute('continue')`` will be
89
  executed synchronously or asynchronously (in gdbscripts it is always
90
  synchronous). Therefore it is recommended to use either the explicitly
91
  synchronous :func:`pwnlib.gdb.Gdb.continue_and_wait` or the explicitly
92
  asynchronous :func:`pwnlib.gdb.Gdb.continue_nowait` instead.
93

94
Tips and Troubleshooting
95
------------------------
96

97
``NOPTRACE`` magic argument
98
~~~~~~~~~~~~~~~~~~~~~~~~~~~
99

100
It's quite cumbersom to comment and un-comment lines containing `attach`.
101

102
You can cause these lines to be a no-op by running your script with the
103
``NOPTRACE`` argument appended, or with ``PWNLIB_NOPTRACE=1`` in the environment.
104

105
::
106

107
    $ python exploit.py NOPTRACE
108
    [+] Starting local process '/bin/bash': Done
109
    [!] Skipping debug attach since context.noptrace==True
110
    ...
111

112
Kernel Yama ptrace_scope
113
~~~~~~~~~~~~~~~~~~~~~~~~
114

115
The Linux kernel v3.4 introduced a security mechanism called ``ptrace_scope``,
116
which is intended to prevent processes from debugging eachother unless there is
117
a direct parent-child relationship.
118

119
This causes some issues with the normal Pwntools workflow, since the process
120
hierarchy looks like this:
121

122
::
123

124
    python ---> target
125
           `--> gdb
126

127
Note that ``python`` is the parent of ``target``, not ``gdb``.
128

129
In order to avoid this being a problem, Pwntools uses the function
130
``prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY)``.  This disables Yama
131
for any processes launched by Pwntools via :class:`.process` or via
132
:meth:`.ssh.process`.
133

134
Older versions of Pwntools did not perform the ``prctl`` step, and
135
required that the Yama security feature was disabled systemwide, which
136
requires ``root`` access.
137

138
Member Documentation
139
===============================
140
"""
141
from __future__ import absolute_import
1✔
142
from __future__ import division
1✔
143

144
from contextlib import contextmanager
1✔
145
import os
1✔
146
import platform
1✔
147
import psutil
1✔
148
import random
1✔
149
import re
1✔
150
import shlex
1✔
151
import six
1✔
152
import six.moves
1✔
153
import socket
1✔
154
import tempfile
1✔
155
from threading import Event
1✔
156
import time
1✔
157

158
from pwnlib import adb
1✔
159
from pwnlib import atexit
1✔
160
from pwnlib import elf
1✔
161
from pwnlib import qemu
1✔
162
from pwnlib import tubes
1✔
163
from pwnlib.asm import _bfdname
1✔
164
from pwnlib.asm import make_elf
1✔
165
from pwnlib.asm import make_elf_from_assembly
1✔
166
from pwnlib.context import LocalContext
1✔
167
from pwnlib.context import context
1✔
168
from pwnlib.log import getLogger
1✔
169
from pwnlib.timeout import Timeout
1✔
170
from pwnlib.util import misc
1✔
171
from pwnlib.util import packing
1✔
172
from pwnlib.util import proc
1✔
173

174
log = getLogger(__name__)
1✔
175

176
@LocalContext
1✔
177
def debug_assembly(asm, gdbscript=None, vma=None, api=False):
1✔
178
    r"""debug_assembly(asm, gdbscript=None, vma=None, api=False) -> tube
179

180
    Creates an ELF file, and launches it under a debugger.
181

182
    This is identical to debug_shellcode, except that
183
    any defined symbols are available in GDB, and it
184
    saves you the explicit call to asm().
185

186
    Arguments:
187
        asm(str): Assembly code to debug
188
        gdbscript(str): Script to run in GDB
189
        vma(int): Base address to load the shellcode at
190
        api(bool): Enable access to GDB Python API
191
        \**kwargs: Override any :obj:`pwnlib.context.context` values.
192

193
    Returns:
194
        :class:`.process`
195

196
    Example:
197

198
    >>> assembly = shellcraft.echo("Hello world!\n")
199
    >>> io = gdb.debug_assembly(assembly)
200
    >>> io.recvline()
201
    b'Hello world!\n'
202
    """
203
    tmp_elf = make_elf_from_assembly(asm, vma=vma, extract=False)
1✔
204
    os.chmod(tmp_elf, 0o777)
1✔
205

206
    atexit.register(lambda: os.unlink(tmp_elf))
1!
207

208
    if context.os == 'android':
1!
209
        android_path = '/data/data/%s' % os.path.basename(tmp_elf)
×
210
        adb.push(tmp_elf, android_path)
×
211
        tmp_elf = android_path
×
212

213
    return debug(tmp_elf, gdbscript=gdbscript, arch=context.arch, api=api)
1✔
214

215
@LocalContext
1✔
216
def debug_shellcode(data, gdbscript=None, vma=None, api=False):
1✔
217
    r"""debug_shellcode(data, gdbscript=None, vma=None, api=False) -> tube
218
    Creates an ELF file, and launches it under a debugger.
219

220
    Arguments:
221
        data(str): Assembled shellcode bytes
222
        gdbscript(str): Script to run in GDB
223
        vma(int): Base address to load the shellcode at
224
        api(bool): Enable access to GDB Python API
225
        \**kwargs: Override any :obj:`pwnlib.context.context` values.
226

227
    Returns:
228
        :class:`.process`
229

230
    Example:
231

232
    >>> assembly = shellcraft.echo("Hello world!\n")
233
    >>> shellcode = asm(assembly)
234
    >>> io = gdb.debug_shellcode(shellcode)
235
    >>> io.recvline()
236
    b'Hello world!\n'
237
    """
238
    if isinstance(data, six.text_type):
1!
239
        log.error("Shellcode is cannot be unicode.  Did you mean debug_assembly?")
×
240
    tmp_elf = make_elf(data, extract=False, vma=vma)
1✔
241
    os.chmod(tmp_elf, 0o777)
1✔
242

243
    atexit.register(lambda: os.unlink(tmp_elf))
1!
244

245
    if context.os == 'android':
1!
246
        android_path = '/data/data/%s' % os.path.basename(tmp_elf)
×
247
        adb.push(tmp_elf, android_path)
×
248
        tmp_elf = android_path
×
249

250
    return debug(tmp_elf, gdbscript=gdbscript, arch=context.arch, api=api)
1✔
251

252
def _gdbserver_args(pid=None, path=None, args=None, which=None, env=None):
1✔
253
    """_gdbserver_args(pid=None, path=None, args=None, which=None, env=None) -> list
254

255
    Sets up a listening gdbserver, to either connect to the specified
256
    PID, or launch the specified binary by its full path.
257

258
    Arguments:
259
        pid(int): Process ID to attach to
260
        path(str): Process to launch
261
        args(list): List of arguments to provide on the debugger command line
262
        which(callaable): Function to find the path of a binary.
263

264
    Returns:
265
        A list of arguments to invoke gdbserver.
266
    """
267
    if [pid, path, args].count(None) != 2:
1!
268
        log.error("Must specify exactly one of pid, path, or args")
×
269

270
    if not which:
1!
271
        log.error("Must specify which.")
×
272

273
    gdbserver = ''
1✔
274

275
    if not args:
1!
276
        args = [str(path or pid)]
×
277

278
    # Android targets have a distinct gdbserver
279
    if context.bits == 64:
1!
280
        gdbserver = which('gdbserver64')
1✔
281

282
    if not gdbserver:
1!
283
        gdbserver = which('gdbserver')
1✔
284

285
    if not gdbserver:
1!
286
        log.error("gdbserver is not installed")
×
287

288
    orig_args = args
1✔
289

290
    gdbserver_args = [gdbserver, '--multi']
1✔
291
    if context.aslr:
1!
292
        gdbserver_args += ['--no-disable-randomization']
1✔
293
    else:
294
        log.warn_once("Debugging process with ASLR disabled")
×
295

296
    if pid:
1!
297
        gdbserver_args += ['--once', '--attach']
×
298

299
    if env is not None:
1!
300
        env_args = []
×
301
        for key in tuple(env):
×
302
            if key.startswith(b'LD_'): # LD_PRELOAD / LD_LIBRARY_PATH etc.
×
303
                env_args.append(b'%s=%s' % (key, env.pop(key)))
×
304
            else:
305
                env_args.append(b'%s=%s' % (key, env[key]))
×
306
        gdbserver_args += ['--wrapper', which('env'), '-i'] + env_args + ['--']
×
307

308
    gdbserver_args += ['localhost:0']
1✔
309
    gdbserver_args += args
1✔
310

311
    return gdbserver_args
1✔
312

313
def _gdbserver_port(gdbserver, ssh):
1✔
314
    which = _get_which(ssh)
1✔
315

316
    # Process /bin/bash created; pid = 14366
317
    # Listening on port 34816
318
    process_created = gdbserver.recvline()
1✔
319

320
    if process_created.startswith(b'ERROR:'):
1!
321
        raise ValueError(
×
322
            'Failed to spawn process under gdbserver. gdbserver error message: %r' % process_created
323
        )
324

325
    try:
1✔
326
        gdbserver.pid   = int(process_created.split()[-1], 0)
1✔
327
    except ValueError:
×
328
        log.error('gdbserver did not output its pid (maybe chmod +x?): %r', process_created)
×
329

330
    listening_on = b''
1✔
331
    while b'Listening' not in listening_on:
1✔
332
        listening_on    = gdbserver.recvline()
1✔
333

334
    port = int(listening_on.split()[-1])
1✔
335

336
    # Set up port forarding for SSH
337
    if ssh:
1✔
338
        remote   = ssh.connect_remote('127.0.0.1', port)
1✔
339
        listener = tubes.listen.listen(0)
1✔
340
        port     = listener.lport
1✔
341

342
        # Disable showing GDB traffic when debugging verbosity is increased
343
        remote.level = 'error'
1✔
344
        listener.level = 'error'
1✔
345

346
        # Hook them up
347
        remote.connect_both(listener)
1✔
348

349
    # Set up port forwarding for ADB
350
    elif context.os == 'android':
1!
351
        adb.forward(port)
×
352

353
    return port
1✔
354

355
def _get_which(ssh=None):
1✔
356
    if ssh:                        return ssh.which
1✔
357
    elif context.os == 'android':  return adb.which
1!
358
    else:                          return misc.which
1✔
359

360
def _get_runner(ssh=None):
1✔
361
    if ssh:                        return ssh.process
1✔
362
    elif context.os == 'android':  return adb.process
1!
363
    else:                          return tubes.process.process
1✔
364

365
@LocalContext
1✔
366
def debug(args, gdbscript=None, exe=None, ssh=None, env=None, sysroot=None, api=False, **kwargs):
1✔
367
    r"""
368
    Launch a GDB server with the specified command line,
369
    and launches GDB to attach to it.
370

371
    Arguments:
372
        args(list): Arguments to the process, similar to :class:`.process`.
373
        gdbscript(str): GDB script to run.
374
        exe(str): Path to the executable on disk
375
        env(dict): Environment to start the binary in
376
        ssh(:class:`.ssh`): Remote ssh session to use to launch the process.
377
        sysroot(str): Set an alternate system root. The system root is used to
378
            load absolute shared library symbol files. This is useful to instruct
379
            gdb to load a local version of binaries/libraries instead of downloading
380
            them from the gdbserver, which is faster
381
        api(bool): Enable access to GDB Python API.
382

383
    Returns:
384
        :class:`.process` or :class:`.ssh_channel`: A tube connected to the target process.
385
        When ``api=True``, ``gdb`` member of the returned object contains a :class:`Gdb`
386
        instance.
387

388
    Notes:
389

390
        The debugger is attached automatically, and you can debug everything
391
        from the very beginning.  This requires that both ``gdb`` and ``gdbserver``
392
        are installed on your machine.
393

394
        When GDB opens via :func:`debug`, it will initially be stopped on the very first
395
        instruction of the dynamic linker (``ld.so``) for dynamically-linked binaries.
396

397
        Only the target binary and the linker will be loaded in memory, so you cannot
398
        set breakpoints on shared library routines like ``malloc`` since ``libc.so``
399
        has not even been loaded yet.
400

401
        There are several ways to handle this:
402

403
        1. Set a breakpoint on the executable's entry point (generally, ``_start``)
404
            - This is only invoked after all of the required shared libraries
405
              are loaded.
406
            - You can generally get the address via the GDB command ``info file``.
407
        2. Use pending breakpoints via ``set breakpoint pending on``
408
            - This has the side-effect of setting breakpoints for **every** function
409
              which matches the name.  For ``malloc``, this will generally set a
410
              breakpoint in the executable's PLT, in the linker's internal ``malloc``,
411
              and eventaully in ``libc``'s malloc.
412
        3. Wait for libraries to be loaded with ``set stop-on-solib-event 1``
413
            - There is no way to stop on any specific library being loaded, and sometimes
414
              multiple libraries are loaded and only a single breakpoint is issued.
415
            - Generally, you just add a few ``continue`` commands until things are set up
416
              the way you want it to be.
417

418
    Examples:
419

420
        Create a new process, and stop it at 'main'
421

422
        >>> io = gdb.debug('bash', '''
423
        ... break main
424
        ... continue
425
        ... ''')
426

427
        Send a command to Bash
428

429
        >>> io.sendline(b"echo hello")
430
        >>> io.recvline()
431
        b'hello\n'
432

433
        Interact with the process
434

435
        >>> io.interactive() # doctest: +SKIP
436
        >>> io.close()
437

438
        Create a new process, and stop it at '_start'
439

440
        >>> io = gdb.debug('bash', '''
441
        ... # Wait until we hit the main executable's entry point
442
        ... break _start
443
        ... continue
444
        ...
445
        ... # Now set breakpoint on shared library routines
446
        ... break malloc
447
        ... break free
448
        ... continue
449
        ... ''')
450

451
        Send a command to Bash
452

453
        >>> io.sendline(b"echo hello")
454
        >>> io.recvline()
455
        b'hello\n'
456

457
        Interact with the process
458

459
        >>> io.interactive() # doctest: +SKIP
460
        >>> io.close()
461

462
    Using GDB Python API:
463

464
    .. doctest
465
       :skipif: six.PY2
466

467
        Debug a new process
468

469
        >>> io = gdb.debug(['echo', 'foo'], api=True)
470

471
        Stop at 'write'
472

473
        >>> bp = io.gdb.Breakpoint('write', temporary=True)
474
        >>> io.gdb.continue_and_wait()
475

476
        Dump 'count'
477

478
        >>> count = io.gdb.parse_and_eval('$rdx')
479
        >>> long = io.gdb.lookup_type('long')
480
        >>> int(count.cast(long))
481
        4
482

483
        Resume the program
484

485
        >>> io.gdb.continue_nowait()
486
        >>> io.recvline()
487
        b'foo\n'
488

489

490
    Using SSH:
491

492
        You can use :func:`debug` to spawn new processes on remote machines as well,
493
        by using the ``ssh=`` keyword to pass in your :class:`.ssh` instance.
494

495
        Connect to the SSH server and start a process on the server
496

497
        >>> shell = ssh('travis', 'example.pwnme', password='demopass')
498
        >>> io = gdb.debug(['whoami'],
499
        ...                 ssh = shell,
500
        ...                 gdbscript = '''
501
        ... break main
502
        ... continue
503
        ... ''')
504

505
        Send a command to Bash
506

507
        >>> io.sendline(b"echo hello")
508

509
        Interact with the process
510
        >>> io.interactive() # doctest: +SKIP
511
        >>> io.close()
512
    """
513
    if isinstance(args, six.integer_types + (tubes.process.process, tubes.ssh.ssh_channel)):
1!
514
        log.error("Use gdb.attach() to debug a running process")
×
515

516
    if isinstance(args, (bytes, six.text_type)):
1✔
517
        args = [args]
1✔
518

519
    orig_args = args
1✔
520

521
    runner = _get_runner(ssh)
1✔
522
    which  = _get_which(ssh)
1✔
523
    gdbscript = gdbscript or ''
1✔
524

525
    if api and runner is not tubes.process.process:
1!
526
        raise ValueError('GDB Python API is supported only for local processes')
×
527

528
    args, env = misc.normalize_argv_env(args, env, log)
1✔
529
    if env:
1!
530
        env = {bytes(k): bytes(v) for k, v in env}
×
531

532
    if context.noptrace:
1!
533
        log.warn_once("Skipping debugger since context.noptrace==True")
×
534
        return runner(args, executable=exe, env=env)
×
535

536
    if ssh or context.native or (context.os == 'android'):
1!
537
        args = _gdbserver_args(args=args, which=which, env=env)
1✔
538
    else:
539
        qemu_port = random.randint(1024, 65535)
×
540
        qemu_user = qemu.user_path()
×
541
        sysroot = sysroot or qemu.ld_prefix(env=env)
×
542
        if not qemu_user:
×
543
            log.error("Cannot debug %s binaries without appropriate QEMU binaries" % context.arch)
×
544
        if context.os == 'baremetal':
×
545
            qemu_args = [qemu_user, '-S', '-gdb', 'tcp::' + str(qemu_port)]
×
546
        else:
547
            qemu_args = [qemu_user, '-g', str(qemu_port)]
×
548
        if sysroot:
×
549
            qemu_args += ['-L', sysroot]
×
550
        args = qemu_args + args
×
551

552
    # Use a sane default sysroot for Android
553
    if not sysroot and context.os == 'android':
1!
554
        sysroot = 'remote:/'
×
555

556
    # Make sure gdbserver/qemu is installed
557
    if not which(args[0]):
1!
558
        log.error("%s is not installed" % args[0])
×
559

560
    if not ssh:
1✔
561
        exe = exe or which(orig_args[0])
1✔
562
        if not (exe and os.path.exists(exe)):
1!
563
            log.error("%s does not exist" % exe)
×
564

565
    # Start gdbserver/qemu
566
    # (Note: We override ASLR here for the gdbserver process itself.)
567
    gdbserver = runner(args, env=env, aslr=1, **kwargs)
1✔
568

569
    # Set the .executable on the process object.
570
    gdbserver.executable = exe
1✔
571

572
    # Find what port we need to connect to
573
    if ssh or context.native or (context.os == 'android'):
1!
574
        port = _gdbserver_port(gdbserver, ssh)
1✔
575
    else:
576
        port = qemu_port
×
577

578
    host = '127.0.0.1'
1✔
579
    if not ssh and context.os == 'android':
1!
580
        host = context.adb_host
×
581

582
    tmp = attach((host, port), exe=exe, gdbscript=gdbscript, ssh=ssh, sysroot=sysroot, api=api)
1✔
583
    if api:
1!
584
        _, gdb = tmp
×
585
        gdbserver.gdb = gdb
×
586

587
    # gdbserver outputs a message when a client connects
588
    garbage = gdbserver.recvline(timeout=1)
1✔
589

590
    # Some versions of gdbserver output an additional message
591
    garbage2 = gdbserver.recvline_startswith(b"Remote debugging from host ", timeout=2)
1✔
592

593
    return gdbserver
1✔
594

595
def get_gdb_arch():
1✔
596
    return {
×
597
        'amd64': 'i386:x86-64',
598
        'powerpc': 'powerpc:common',
599
        'powerpc64': 'powerpc:common64',
600
        'mips64': 'mips:isa64',
601
        'thumb': 'arm',
602
        'sparc64': 'sparc:v9'
603
    }.get(context.arch, context.arch)
604

605
def binary():
1✔
606
    """binary() -> str
607

608
    Returns:
609
        str: Path to the appropriate ``gdb`` binary to use.
610

611
    Example:
612

613
        >>> gdb.binary() # doctest: +SKIP
614
        '/usr/bin/gdb'
615
    """
616
    gdb = misc.which('pwntools-gdb') or misc.which('gdb')
1✔
617

618
    if not context.native:
1!
619
        multiarch = misc.which('gdb-multiarch')
×
620

621
        if multiarch:
×
622
            return multiarch
×
623
        log.warn_once('Cross-architecture debugging usually requires gdb-multiarch\n'
×
624
                      '$ apt-get install gdb-multiarch')
625

626
    if not gdb:
1!
627
        log.error('GDB is not installed\n'
×
628
                  '$ apt-get install gdb')
629

630
    return gdb
1✔
631

632
class Breakpoint:
1✔
633
    """Mirror of ``gdb.Breakpoint`` class.
634

635
    See https://sourceware.org/gdb/onlinedocs/gdb/Breakpoints-In-Python.html
636
    for more information.
637
    """
638

639
    def __init__(self, conn, *args, **kwargs):
1✔
640
        """Do not create instances of this class directly.
641

642
        Use ``pwnlib.gdb.Gdb.Breakpoint`` instead.
643
        """
644
        # Creates a real breakpoint and connects it with this mirror
645
        self.conn = conn
×
646
        self.server_breakpoint = conn.root.set_breakpoint(
×
647
            self, hasattr(self, 'stop'), *args, **kwargs)
648

649
    def __getattr__(self, item):
1✔
650
        """Return attributes of the real breakpoint."""
651
        if item in (
×
652
                '____id_pack__',
653
                '__name__',
654
                '____conn__',
655
                'stop',
656
        ):
657
            # Ignore RPyC netref attributes.
658
            # Also, if stop() is not defined, hasattr() call in our
659
            # __init__() will bring us here. Don't contact the
660
            # server in this case either.
661
            raise AttributeError()
×
662
        return getattr(self.server_breakpoint, item)
×
663

664
    def exposed_stop(self):
1✔
665
        # Handle stop() call from the server.
666
        return self.stop()
×
667

668
class FinishBreakpoint:
1✔
669
    """Mirror of ``gdb.FinishBreakpoint`` class.
670

671
    See https://sourceware.org/gdb/onlinedocs/gdb/Finish-Breakpoints-in-Python.html
672
    for more information.
673
    """
674

675
    def __init__(self, conn, *args, **kwargs):
1✔
676
        """Do not create instances of this class directly.
677

678
        Use ``pwnlib.gdb.Gdb.FinishBreakpoint`` instead.
679
        """
680
        # Creates a real finish breakpoint and connects it with this mirror
681
        self.conn = conn
×
682
        self.server_breakpoint = conn.root.set_finish_breakpoint(
×
683
            self, hasattr(self, 'stop'), hasattr(self, 'out_of_scope'),
684
            *args, **kwargs)
685

686
    def __getattr__(self, item):
1✔
687
        """Return attributes of the real breakpoint."""
688
        if item in (
×
689
                '____id_pack__',
690
                '__name__',
691
                '____conn__',
692
                'stop',
693
                'out_of_scope',
694
        ):
695
            # Ignore RPyC netref attributes.
696
            # Also, if stop() or out_of_scope() are not defined, hasattr() call
697
            # in our __init__() will bring us here. Don't contact the
698
            # server in this case either.
699
            raise AttributeError()
×
700
        return getattr(self.server_breakpoint, item)
×
701

702
    def exposed_stop(self):
1✔
703
        # Handle stop() call from the server.
704
        return self.stop()
×
705

706
    def exposed_out_of_scope(self):
1✔
707
        # Handle out_of_scope() call from the server.
708
        return self.out_of_scope()
×
709

710
class Gdb:
1✔
711
    """Mirror of ``gdb`` module.
712

713
    See https://sourceware.org/gdb/onlinedocs/gdb/Basic-Python.html for more
714
    information.
715
    """
716

717
    def __init__(self, conn):
1✔
718
        """Do not create instances of this class directly.
719

720
        Use :func:`attach` or :func:`debug` with ``api=True`` instead.
721
        """
722
        self.conn = conn
×
723

724
        class _Breakpoint(Breakpoint):
×
725
            def __init__(self, *args, **kwargs):
×
726
                super().__init__(conn, *args, **kwargs)
×
727
        class _FinishBreakpoint(FinishBreakpoint):
×
728
            def __init__(self, *args, **kwargs):
×
729
                super().__init__(conn, *args, **kwargs)
×
730

731
        self.Breakpoint = _Breakpoint
×
732
        self.FinishBreakpoint = _FinishBreakpoint
×
733
        self.stopped = Event()
×
734

735
        def stop_handler(event):
×
736
            self.stopped.set()
×
737

738
        self.events.stop.connect(stop_handler)
×
739

740
    def __getattr__(self, item):
1✔
741
        """Provide access to the attributes of `gdb` module."""
742
        return getattr(self.conn.root.gdb, item)
×
743

744
    def wait(self):
1✔
745
        """Wait until the program stops."""
746
        self.stopped.wait()
×
747
        self.stopped.clear()
×
748

749
    def interrupt_and_wait(self):
1✔
750
        """Interrupt the program and wait until it stops."""
751
        self.execute('interrupt')
×
752
        self.wait()
×
753

754
    def continue_nowait(self):
1✔
755
        """Continue the program. Do not wait until it stops again."""
756
        self.execute('continue &')
×
757

758
    def continue_and_wait(self):
1✔
759
        """Continue the program and wait until it stops again."""
760
        self.continue_nowait()
×
761
        self.wait()
×
762

763
    def quit(self):
1✔
764
        """Terminate GDB."""
765
        self.conn.root.quit()
×
766

767
@LocalContext
1✔
768
def attach(target, gdbscript = '', exe = None, gdb_args = None, ssh = None, sysroot = None, api = False):
1✔
769
    r"""
770
    Start GDB in a new terminal and attach to `target`.
771

772
    Arguments:
773
        target: The target to attach to.
774
        gdbscript(:obj:`str` or :obj:`file`): GDB script to run after attaching.
775
        exe(str): The path of the target binary.
776
        arch(str): Architechture of the target binary.  If `exe` known GDB will
777
          detect the architechture automatically (if it is supported).
778
        gdb_args(list): List of additional arguments to pass to GDB.
779
        sysroot(str): Set an alternate system root. The system root is used to
780
            load absolute shared library symbol files. This is useful to instruct
781
            gdb to load a local version of binaries/libraries instead of downloading
782
            them from the gdbserver, which is faster
783
        api(bool): Enable access to GDB Python API.
784

785
    Returns:
786
        PID of the GDB process (or the window which it is running in).
787
        When ``api=True``, a (PID, :class:`Gdb`) tuple.
788

789
    Notes:
790

791
        The ``target`` argument is very robust, and can be any of the following:
792

793
        :obj:`int`
794
            PID of a process
795
        :obj:`str`
796
            Process name.  The youngest process is selected.
797
        :obj:`tuple`
798
            Host, port pair of a listening ``gdbserver``
799
        :class:`.process`
800
            Process to connect to
801
        :class:`.sock`
802
            Connected socket. The executable on the other end of the connection is attached to.
803
            Can be any socket type, including :class:`.listen` or :class:`.remote`.
804
        :class:`.ssh_channel`
805
            Remote process spawned via :meth:`.ssh.process`.
806
            This will use the GDB installed on the remote machine.
807
            If a password is required to connect, the ``sshpass`` program must be installed.
808

809
    Examples:
810

811
        Attach to a process by PID
812

813
        >>> pid = gdb.attach(1234) # doctest: +SKIP
814

815
        Attach to the youngest process by name
816

817
        >>> pid = gdb.attach('bash') # doctest: +SKIP
818

819
        Attach a debugger to a :class:`.process` tube and automate interaction
820

821
        >>> io = process('bash')
822
        >>> pid = gdb.attach(io, gdbscript='''
823
        ... call puts("Hello from process debugger!")
824
        ... detach
825
        ... quit
826
        ... ''')
827
        >>> io.recvline()
828
        b'Hello from process debugger!\n'
829
        >>> io.sendline(b'echo Hello from bash && exit')
830
        >>> io.recvall()
831
        b'Hello from bash\n'
832

833
        Using GDB Python API:
834

835
        .. doctest
836
           :skipif: six.PY2
837

838
            >>> io = process('bash')
839

840
            Attach a debugger
841

842
            >>> pid, io_gdb = gdb.attach(io, api=True)
843

844
            Force the program to write something it normally wouldn't
845

846
            >>> io_gdb.execute('call puts("Hello from process debugger!")')
847

848
            Resume the program
849

850
            >>> io_gdb.continue_nowait()
851

852
            Observe the forced line
853

854
            >>> io.recvline()
855
            b'Hello from process debugger!\n'
856

857
            Interact with the program in a regular way
858

859
            >>> io.sendline(b'echo Hello from bash && exit')
860

861
            Observe the results
862

863
            >>> io.recvall()
864
            b'Hello from bash\n'
865

866
        Attach to the remote process from a :class:`.remote` or :class:`.listen` tube,
867
        as long as it is running on the same machine.
868

869
        >>> server = process(['socat', 'tcp-listen:12345,reuseaddr,fork', 'exec:/bin/bash,nofork'])
870
        >>> sleep(1) # Wait for socat to start
871
        >>> io = remote('127.0.0.1', 12345)
872
        >>> sleep(1) # Wait for process to fork
873
        >>> pid = gdb.attach(io, gdbscript='''
874
        ... call puts("Hello from remote debugger!")
875
        ... detach
876
        ... quit
877
        ... ''')
878
        >>> io.recvline()
879
        b'Hello from remote debugger!\n'
880
        >>> io.sendline(b'echo Hello from bash && exit')
881
        >>> io.recvall()
882
        b'Hello from bash\n'
883

884
        Attach to processes running on a remote machine via an SSH :class:`.ssh` process
885

886
        >>> shell = ssh('travis', 'example.pwnme', password='demopass')
887
        >>> io = shell.process(['cat'])
888
        >>> pid = gdb.attach(io, gdbscript='''
889
        ... call sleep(5)
890
        ... call puts("Hello from ssh debugger!")
891
        ... detach
892
        ... quit
893
        ... ''')
894
        >>> io.recvline(timeout=5)  # doctest: +SKIP
895
        b'Hello from ssh debugger!\n'
896
        >>> io.sendline(b'This will be echoed back')
897
        >>> io.recvline()
898
        b'This will be echoed back\n'
899
        >>> io.close()
900
    """
901
    if context.noptrace:
1!
902
        log.warn_once("Skipping debug attach since context.noptrace==True")
×
903
        return
×
904

905
    # if gdbscript is a file object, then read it; we probably need to run some
906
    # more gdb script anyway
907
    if hasattr(gdbscript, 'read'):
1!
908
        with gdbscript:
×
909
            gdbscript = gdbscript.read()
×
910

911
    # enable gdb.attach(p, 'continue')
912
    if gdbscript and not gdbscript.endswith('\n'):
1!
913
        gdbscript += '\n'
×
914

915
    # Use a sane default sysroot for Android
916
    if not sysroot and context.os == 'android':
1!
917
        sysroot = 'remote:/'
×
918

919
    # gdb script to run before `gdbscript`
920
    pre = ''
1✔
921
    if sysroot:
1!
922
        pre += 'set sysroot %s\n' % sysroot
×
923
    if not context.native:
1!
924
        pre += 'set endian %s\n' % context.endian
×
925
        pre += 'set architecture %s\n' % get_gdb_arch()
×
926

927
        if context.os == 'android':
×
928
            pre += 'set gnutarget ' + _bfdname() + '\n'
×
929

930
        if exe and context.os != 'baremetal':
×
931
            pre += 'file "%s"\n' % exe
×
932

933
    # let's see if we can find a pid to attach to
934
    pid = None
1✔
935
    if   isinstance(target, six.integer_types):
1!
936
        # target is a pid, easy peasy
937
        pid = target
×
938
    elif isinstance(target, str):
1!
939
        # pidof picks the youngest process
940
        pidof = proc.pidof
×
941

942
        if context.os == 'android':
×
943
            pidof = adb.pidof
×
944

945
        pids = list(pidof(target))
×
946
        if not pids:
×
947
            log.error('No such process: %s', target)
×
948
        pid = pids[0]
×
949
        log.info('Attaching to youngest process "%s" (PID = %d)' %
×
950
                 (target, pid))
951
    elif isinstance(target, tubes.ssh.ssh_channel):
1✔
952
        if not target.pid:
1!
953
            log.error("PID unknown for channel")
×
954

955
        shell = target.parent
1✔
956

957
        tmpfile = shell.mktemp()
1✔
958
        gdbscript = b'shell rm %s\n%s' % (tmpfile, packing._need_bytes(gdbscript, 2, 0x80))
1✔
959
        shell.upload_data(gdbscript or b'', tmpfile)
1✔
960

961
        cmd = ['ssh', '-C', '-t', '-p', str(shell.port), '-l', shell.user, shell.host]
1✔
962
        if shell.password:
1!
963
            if not misc.which('sshpass'):
1!
964
                log.error("sshpass must be installed to debug ssh processes")
×
965
            cmd = ['sshpass', '-p', shell.password] + cmd
1✔
966
        if shell.keyfile:
1!
967
            cmd += ['-i', shell.keyfile]
×
968
        cmd += ['gdb', '-q', target.executable, str(target.pid), '-x', tmpfile]
1✔
969

970
        misc.run_in_new_terminal(cmd)
1✔
971
        return
1✔
972

973
    elif isinstance(target, tubes.sock.sock):
1✔
974
        pids = proc.pidof(target)
1✔
975
        if not pids:
1!
976
            log.error('Could not find remote process (%s:%d) on this machine' %
×
977
                      target.sock.getpeername())
978
        pid = pids[0]
1✔
979

980
        # Specifically check for socat, since it has an intermediary process
981
        # if you do not specify "nofork" to the EXEC: argument
982
        # python(2640)───socat(2642)───socat(2643)───bash(2644)
983
        if proc.exe(pid).endswith('/socat') and time.sleep(0.1) and proc.children(pid):
1!
984
            pid = proc.children(pid)[0]
×
985

986
        # We may attach to the remote process after the fork but before it performs an exec.  
987
        # If an exe is provided, wait until the process is actually running the expected exe
988
        # before we attach the debugger.
989
        t = Timeout()
1✔
990
        with t.countdown(2):
1✔
991
            while exe and os.path.realpath(proc.exe(pid)) != os.path.realpath(exe) and t.timeout:
1!
992
                time.sleep(0.1)
×
993

994
    elif isinstance(target, tubes.process.process):
1✔
995
        pid = proc.pidof(target)[0]
1✔
996
        exe = exe or target.executable
1✔
997
    elif isinstance(target, tuple) and len(target) == 2:
1!
998
        host, port = target
1✔
999

1000
        if context.os != 'android':
1!
1001
            pre += 'target remote %s:%d\n' % (host, port)
1✔
1002
        else:
1003
            # Android debugging is done over gdbserver, which can't follow
1004
            # new inferiors (tldr; follow-fork-mode child) unless it is run
1005
            # in extended-remote mode.
1006
            pre += 'target extended-remote %s:%d\n' % (host, port)
×
1007
            pre += 'set detach-on-fork off\n'
×
1008

1009
        def findexe():
1✔
1010
            for spid in proc.pidof(target):
1!
1011
                sexe = proc.exe(spid)
×
1012
                name = os.path.basename(sexe)
×
1013
                # XXX: parse cmdline
1014
                if name.startswith('qemu-') or name.startswith('gdbserver'):
×
1015
                    exe = proc.cmdline(spid)[-1]
×
1016
                    return os.path.join(proc.cwd(spid), exe)
×
1017

1018
        exe = exe or findexe()
1✔
1019
    elif isinstance(target, elf.corefile.Corefile):
×
1020
        pre += 'target core "%s"\n' % target.path
×
1021
    else:
1022
        log.error("don't know how to attach to target: %r", target)
×
1023

1024
    # if we have a pid but no exe, just look it up in /proc/
1025
    if pid and not exe:
1✔
1026
        exe_fn = proc.exe
1✔
1027
        if context.os == 'android':
1!
1028
            exe_fn = adb.proc_exe
×
1029
        exe = exe_fn(pid)
1✔
1030

1031
    if not pid and not exe and not ssh:
1!
1032
        log.error('could not find target process')
×
1033

1034
    gdb_binary = binary()
1✔
1035
    cmd = [gdb_binary]
1✔
1036

1037
    if gdb_args:
1✔
1038
        cmd += gdb_args
1✔
1039

1040
    if context.gdbinit:
1!
1041
        cmd += ['-nh']                  # ignore ~/.gdbinit
×
1042
        cmd += ['-x', context.gdbinit]  # load custom gdbinit
×
1043

1044
    cmd += ['-q']
1✔
1045

1046
    if exe and context.native:
1✔
1047
        if not ssh and not os.path.isfile(exe):
1!
1048
            log.error('No such file: %s', exe)
×
1049
        cmd += [exe]
1✔
1050

1051
    if pid and not context.os == 'android':
1✔
1052
        cmd += [str(pid)]
1✔
1053

1054
    if context.os == 'android' and pid:
1!
1055
        runner  = _get_runner()
×
1056
        which   = _get_which()
×
1057
        gdb_cmd = _gdbserver_args(pid=pid, which=which)
×
1058
        gdbserver = runner(gdb_cmd)
×
1059
        port    = _gdbserver_port(gdbserver, None)
×
1060
        host    = context.adb_host
×
1061
        pre    += 'target extended-remote %s:%i\n' % (context.adb_host, port)
×
1062

1063
        # gdbserver on Android sets 'detach-on-fork on' which breaks things
1064
        # when you're trying to debug anything that forks.
1065
        pre += 'set detach-on-fork off\n'
×
1066

1067
    if api:
1!
1068
        # create a UNIX socket for talking to GDB
1069
        socket_dir = tempfile.mkdtemp()
×
1070
        socket_path = os.path.join(socket_dir, 'socket')
×
1071
        bridge = os.path.join(os.path.dirname(__file__), 'gdb_api_bridge.py')
×
1072

1073
        # inject the socket path and the GDB Python API bridge
1074
        pre = 'python socket_path = ' + repr(socket_path) + '\n' + \
×
1075
              'source ' + bridge + '\n' + \
1076
              pre
1077

1078
    gdbscript = pre + (gdbscript or '')
1✔
1079

1080
    if gdbscript:
1✔
1081
        tmp = tempfile.NamedTemporaryFile(prefix = 'pwn', suffix = '.gdb',
1✔
1082
                                          delete = False, mode = 'w+')
1083
        log.debug('Wrote gdb script to %r\n%s', tmp.name, gdbscript)
1✔
1084
        gdbscript = 'shell rm %s\n%s' % (tmp.name, gdbscript)
1✔
1085

1086
        tmp.write(gdbscript)
1✔
1087
        tmp.close()
1✔
1088
        cmd += ['-x', tmp.name]
1✔
1089

1090
    log.info('running in new terminal: %s', cmd)
1✔
1091

1092
    if api:
1!
1093
        # prevent gdb_faketerminal.py from messing up api doctests
1094
        def preexec_fn():
×
1095
            os.environ['GDB_FAKETERMINAL'] = '0'
×
1096
    else:
1097
        preexec_fn = None
1✔
1098
    gdb_pid = misc.run_in_new_terminal(cmd, preexec_fn = preexec_fn)
1✔
1099

1100
    if pid and context.native:
1✔
1101
        proc.wait_for_debugger(pid, gdb_pid)
1✔
1102

1103
    if not api:
1!
1104
        return gdb_pid
1✔
1105

1106
    # connect to the GDB Python API bridge
1107
    from rpyc import BgServingThread
×
1108
    from rpyc.utils.factory import unix_connect
×
1109
    if six.PY2:
×
1110
        retriable = socket.error
×
1111
    else:
1112
        retriable = ConnectionRefusedError, FileNotFoundError
×
1113

1114
    t = Timeout()
×
1115
    with t.countdown(10):
×
1116
        while t.timeout:
×
1117
            try:
×
1118
                conn = unix_connect(socket_path)
×
1119
                break
×
1120
            except retriable:
×
1121
                time.sleep(0.1)
×
1122
        else:
1123
            # Check to see if RPyC is installed at all in GDB
1124
            rpyc_check = [gdb_binary, '--nx', '-batch', '-ex',
×
1125
                          'python import rpyc; import sys; sys.exit(123)']
1126

1127
            if 123 != tubes.process.process(rpyc_check).poll(block=True):
×
1128
                log.error('Failed to connect to GDB: rpyc is not installed')
×
1129

1130
            # Check to see if the socket ever got created
1131
            if not os.path.exists(socket_path):
×
1132
                log.error('Failed to connect to GDB: Unix socket %s was never created', socket_path)
×
1133

1134
            # Check to see if the remote RPyC client is a compatible version
1135
            version_check = [gdb_binary, '--nx', '-batch', '-ex',
×
1136
                            'python import platform; print(platform.python_version())']
1137
            gdb_python_version = tubes.process.process(version_check).recvall().strip()
×
1138
            python_version = str(platform.python_version())
×
1139

1140
            if gdb_python_version != python_version:
×
1141
                log.error('Failed to connect to GDB: Version mismatch (%s vs %s)',
×
1142
                           gdb_python_version,
1143
                           python_version)
1144

1145
            # Don't know what happened
1146
            log.error('Failed to connect to GDB: Unknown error')
×
1147

1148
    # now that connection is up, remove the socket from the filesystem
1149
    os.unlink(socket_path)
×
1150
    os.rmdir(socket_dir)
×
1151

1152
    # create a thread for receiving breakpoint notifications
1153
    BgServingThread(conn, callback=lambda: None)
×
1154

1155
    return gdb_pid, Gdb(conn)
×
1156

1157

1158
def ssh_gdb(ssh, argv, gdbscript = None, arch = None, **kwargs):
1✔
1159
    if not isinstance(argv, (list, tuple)):
×
1160
        argv = [argv]
×
1161

1162
    exe = argv[0]
×
1163
    argv = ["gdbserver", "--multi", "127.0.0.1:0"] + argv
×
1164

1165
    # Download the executable
1166
    local_exe = os.path.basename(exe)
×
1167
    ssh.download_file(ssh.which(exe), local_exe)
×
1168

1169
    # Run the process
1170
    c = ssh.process(argv, **kwargs)
×
1171

1172
    # Find the port for the gdb server
1173
    c.recvuntil(b'port ')
×
1174
    line = c.recvline().strip()
×
1175
    gdbport = re.match(b'[0-9]+', line)
×
1176
    if gdbport:
×
1177
        gdbport = int(gdbport.group(0))
×
1178

1179
    l = tubes.listen.listen(0)
×
1180
    forwardport = l.lport
×
1181

1182
    attach(('127.0.0.1', forwardport), gdbscript, local_exe, arch, ssh=ssh)
×
1183
    l.wait_for_connection().connect_both(ssh.connect_remote('127.0.0.1', gdbport))
×
1184
    return c
×
1185

1186
def find_module_addresses(binary, ssh=None, ulimit=False):
1✔
1187
    """
1188
    Cheat to find modules by using GDB.
1189

1190
    We can't use ``/proc/$pid/map`` since some servers forbid it.
1191
    This breaks ``info proc`` in GDB, but ``info sharedlibrary`` still works.
1192
    Additionally, ``info sharedlibrary`` works on FreeBSD, which may not have
1193
    procfs enabled or accessible.
1194

1195
    The output looks like this:
1196

1197
    ::
1198

1199
        info proc mapping
1200
        process 13961
1201
        warning: unable to open /proc file '/proc/13961/maps'
1202

1203
        info sharedlibrary
1204
        From        To          Syms Read   Shared Object Library
1205
        0xf7fdc820  0xf7ff505f  Yes (*)     /lib/ld-linux.so.2
1206
        0xf7fbb650  0xf7fc79f8  Yes         /lib32/libpthread.so.0
1207
        0xf7e26f10  0xf7f5b51c  Yes (*)     /lib32/libc.so.6
1208
        (*): Shared library is missing debugging information.
1209

1210
    Note that the raw addresses provided by ``info sharedlibrary`` are actually
1211
    the address of the ``.text`` segment, not the image base address.
1212

1213
    This routine automates the entire process of:
1214

1215
    1. Downloading the binaries from the remote server
1216
    2. Scraping GDB for the information
1217
    3. Loading each library into an ELF
1218
    4. Fixing up the base address vs. the ``.text`` segment address
1219

1220
    Arguments:
1221
        binary(str): Path to the binary on the remote server
1222
        ssh(pwnlib.tubes.tube): SSH connection through which to load the libraries.
1223
            If left as :const:`None`, will use a :class:`pwnlib.tubes.process.process`.
1224
        ulimit(bool): Set to :const:`True` to run "ulimit -s unlimited" before GDB.
1225

1226
    Returns:
1227
        A list of pwnlib.elf.ELF objects, with correct base addresses.
1228

1229
    Example:
1230

1231
    >>> with context.local(log_level=9999):
1232
    ...     shell =  ssh(host='example.pwnme', user='travis', password='demopass')
1233
    ...     bash_libs = gdb.find_module_addresses('/bin/bash', shell)
1234
    >>> os.path.basename(bash_libs[0].path)
1235
    'libc.so.6'
1236
    >>> hex(bash_libs[0].symbols['system']) # doctest: +SKIP
1237
    '0x7ffff7634660'
1238
    """
1239
    #
1240
    # Download all of the remote libraries
1241
    #
1242
    if ssh:
1!
1243
        runner     = ssh.run
1✔
1244
        local_bin  = ssh.download_file(binary)
1✔
1245
        local_elf  = elf.ELF(os.path.basename(binary))
1✔
1246
        local_libs = ssh.libs(binary)
1✔
1247

1248
    else:
1249
        runner     = tubes.process.process
×
1250
        local_elf  = elf.ELF(binary)
×
1251
        local_libs = local_elf.libs
×
1252

1253
    #
1254
    # Get the addresses from GDB
1255
    #
1256
    libs = {}
1✔
1257
    cmd  = "gdb -q -nh --args %s | cat" % (binary) # pipe through cat to disable colored output on GDB 9+
1✔
1258
    expr = re.compile(r'(0x\S+)[^/]+(.*)')
1✔
1259

1260
    if ulimit:
1!
1261
        cmd = ['sh', '-c', "(ulimit -s unlimited; %s)" % cmd]
×
1262
    else:
1263
        cmd = ['sh', '-c', cmd]
1✔
1264

1265
    with runner(cmd) as gdb:
1✔
1266
        if context.aslr:
1!
1267
            gdb.sendline(b'set disable-randomization off')
1✔
1268

1269
        gdb.send(b"""\
1✔
1270
        set prompt
1271
        catch load
1272
        run
1273
        """)
1274
        gdb.sendline(b'info sharedlibrary')
1✔
1275
        lines = packing._decode(gdb.recvrepeat(2))
1✔
1276

1277
        for line in lines.splitlines():
1✔
1278
            m = expr.match(line)
1✔
1279
            if m:
1✔
1280
                libs[m.group(2)] = int(m.group(1),16)
1✔
1281
        gdb.sendline(b'kill')
1✔
1282
        gdb.sendline(b'y')
1✔
1283
        gdb.sendline(b'quit')
1✔
1284

1285
    #
1286
    # Fix up all of the addresses against the .text address
1287
    #
1288
    rv = []
1✔
1289

1290
    for remote_path,text_address in sorted(libs.items()):
1✔
1291
        # Match up the local copy to the remote path
1292
        try:
1✔
1293
            path     = next(p for p in local_libs.keys() if remote_path in p)
1!
1294
        except StopIteration:
×
1295
            print("Skipping %r" % remote_path)
×
1296
            continue
×
1297

1298
        # Load it
1299
        lib      = elf.ELF(path)
1✔
1300

1301
        # Find its text segment
1302
        text     = lib.get_section_by_name('.text')
1✔
1303

1304
        # Fix the address
1305
        lib.address = text_address - text.header.sh_addr
1✔
1306
        rv.append(lib)
1✔
1307

1308
    return rv
1✔
1309

1310
def corefile(process):
1✔
1311
    r"""Drops a core file for a running local process.
1312

1313
    Note:
1314
        You should use :meth:`.process.corefile` instead of using this method directly.
1315

1316
    Arguments:
1317
        process: Process to dump
1318

1319
    Returns:
1320
        :class:`.Core`: The generated core file
1321

1322
    Example:
1323

1324
        >>> io = process('bash')
1325
        >>> core = gdb.corefile(io)
1326
        >>> core.exe.name # doctest: +ELLIPSIS
1327
        '.../bin/bash'
1328
    """
1329

1330
    if context.noptrace:
1!
1331
        log.warn_once("Skipping corefile since context.noptrace==True")
×
1332
        return
×
1333

1334
    corefile_path = './core.%s.%i' % (os.path.basename(process.executable),
1✔
1335
                                    process.pid)
1336

1337
    # Due to https://sourceware.org/bugzilla/show_bug.cgi?id=16092
1338
    # will disregard coredump_filter, and will not dump private mappings.
1339
    if version() < (7,11):
1!
1340
        log.warn_once('The installed GDB (%s) does not emit core-dumps which '
×
1341
                      'contain all of the data in the process.\n'
1342
                      'Upgrade to GDB >= 7.11 for better core-dumps.' % binary())
1343

1344
    # This is effectively the same as what the 'gcore' binary does
1345
    gdb_args = ['-batch',
1✔
1346
                '-q',
1347
                '-nx',
1348
                '-ex', 'set pagination off',
1349
                '-ex', 'set height 0',
1350
                '-ex', 'set width 0',
1351
                '-ex', 'set use-coredump-filter on',
1352
                '-ex', 'generate-core-file %s' % corefile_path,
1353
                '-ex', 'detach']
1354

1355
    with context.local(terminal = ['sh', '-c']):
1✔
1356
        with context.quiet:
1✔
1357
            pid = attach(process, gdb_args=gdb_args)
1✔
1358
            log.debug("Got GDB pid %d", pid)
1✔
1359
            try:
1✔
1360
                psutil.Process(pid).wait()
1✔
1361
            except psutil.Error:
×
1362
                pass
×
1363

1364
    if not os.path.exists(corefile_path):
1!
1365
        log.error("Could not generate a corefile for process %d", process.pid)
×
1366

1367
    return elf.corefile.Core(corefile_path)
1✔
1368

1369
def version(program='gdb'):
1✔
1370
    """Gets the current GDB version.
1371

1372
    Note:
1373
        Requires that GDB version meets the following format:
1374

1375
        ``GNU gdb (GDB) 7.12``
1376

1377
    Returns:
1378
        tuple: A tuple containing the version numbers
1379

1380
    Example:
1381

1382
        >>> (7,0) <= gdb.version() <= (19,0)
1383
        True
1384
    """
1385
    program = misc.which(program)
1✔
1386
    expr = br'([0-9]+\.?)+'
1✔
1387

1388
    with tubes.process.process([program, '--version'], level='error', stdout=tubes.process.PIPE) as gdb:
1✔
1389
        version = gdb.recvline()
1✔
1390

1391
    versions = re.search(expr, version).group()
1✔
1392

1393
    return tuple(map(int, versions.split(b'.')))
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc