| # Pretty printers for the NPTL lock types. |
| # |
| # Copyright (C) 2016-2018 Free Software Foundation, Inc. |
| # This file is part of the GNU C Library. |
| # |
| # The GNU C Library is free software; you can redistribute it and/or |
| # modify it under the terms of the GNU Lesser General Public |
| # License as published by the Free Software Foundation; either |
| # version 2.1 of the License, or (at your option) any later version. |
| # |
| # The GNU C Library is distributed in the hope that it will be useful, |
| # but WITHOUT ANY WARRANTY; without even the implied warranty of |
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| # Lesser General Public License for more details. |
| # |
| # You should have received a copy of the GNU Lesser General Public |
| # License along with the GNU C Library; if not, see |
| # <http://www.gnu.org/licenses/>. |
| |
| """This file contains the gdb pretty printers for the following types: |
| |
| * pthread_mutex_t |
| * pthread_mutexattr_t |
| * pthread_cond_t |
| * pthread_condattr_t |
| * pthread_rwlock_t |
| * pthread_rwlockattr_t |
| |
| You can check which printers are registered and enabled by issuing the |
| 'info pretty-printer' gdb command. Printers should trigger automatically when |
| trying to print a variable of one of the types mentioned above. |
| """ |
| |
| from __future__ import print_function |
| |
| import gdb |
| import gdb.printing |
| from nptl_lock_constants import * |
| |
| MUTEX_TYPES = { |
| PTHREAD_MUTEX_NORMAL: ('Type', 'Normal'), |
| PTHREAD_MUTEX_RECURSIVE: ('Type', 'Recursive'), |
| PTHREAD_MUTEX_ERRORCHECK: ('Type', 'Error check'), |
| PTHREAD_MUTEX_ADAPTIVE_NP: ('Type', 'Adaptive') |
| } |
| |
| class MutexPrinter(object): |
| """Pretty printer for pthread_mutex_t.""" |
| |
| def __init__(self, mutex): |
| """Initialize the printer's internal data structures. |
| |
| Args: |
| mutex: A gdb.value representing a pthread_mutex_t. |
| """ |
| |
| data = mutex['__data'] |
| self.lock = data['__lock'] |
| self.count = data['__count'] |
| self.owner = data['__owner'] |
| self.kind = data['__kind'] |
| self.values = [] |
| self.read_values() |
| |
| def to_string(self): |
| """gdb API function. |
| |
| This is called from gdb when we try to print a pthread_mutex_t. |
| """ |
| |
| return 'pthread_mutex_t' |
| |
| def children(self): |
| """gdb API function. |
| |
| This is called from gdb when we try to print a pthread_mutex_t. |
| """ |
| |
| return self.values |
| |
| def read_values(self): |
| """Read the mutex's info and store it in self.values. |
| |
| The data contained in self.values will be returned by the Iterator |
| created in self.children. |
| """ |
| |
| self.read_type() |
| self.read_status() |
| self.read_attributes() |
| self.read_misc_info() |
| |
| def read_type(self): |
| """Read the mutex's type.""" |
| |
| mutex_type = self.kind & PTHREAD_MUTEX_KIND_MASK |
| |
| # mutex_type must be casted to int because it's a gdb.Value |
| self.values.append(MUTEX_TYPES[int(mutex_type)]) |
| |
| def read_status(self): |
| """Read the mutex's status. |
| |
| Architectures that support lock elision might not record the mutex owner |
| ID in the __owner field. In that case, the owner will be reported as |
| "Unknown". |
| """ |
| |
| if self.kind == PTHREAD_MUTEX_DESTROYED: |
| self.values.append(('Status', 'Destroyed')) |
| elif self.kind & PTHREAD_MUTEX_ROBUST_NORMAL_NP: |
| self.read_status_robust() |
| else: |
| self.read_status_no_robust() |
| |
| def read_status_robust(self): |
| """Read the status of a robust mutex. |
| |
| In glibc robust mutexes are implemented in a very different way than |
| non-robust ones. This method reads their locking status, |
| whether it may have waiters, their registered owner (if any), |
| whether the owner is alive or not, and the status of the state |
| they're protecting. |
| """ |
| |
| if self.lock == PTHREAD_MUTEX_UNLOCKED: |
| self.values.append(('Status', 'Not acquired')) |
| else: |
| if self.lock & FUTEX_WAITERS: |
| self.values.append(('Status', |
| 'Acquired, possibly with waiters')) |
| else: |
| self.values.append(('Status', |
| 'Acquired, possibly with no waiters')) |
| |
| if self.lock & FUTEX_OWNER_DIED: |
| self.values.append(('Owner ID', '%d (dead)' % self.owner)) |
| else: |
| self.values.append(('Owner ID', self.lock & FUTEX_TID_MASK)) |
| |
| if self.owner == PTHREAD_MUTEX_INCONSISTENT: |
| self.values.append(('State protected by this mutex', |
| 'Inconsistent')) |
| elif self.owner == PTHREAD_MUTEX_NOTRECOVERABLE: |
| self.values.append(('State protected by this mutex', |
| 'Not recoverable')) |
| |
| def read_status_no_robust(self): |
| """Read the status of a non-robust mutex. |
| |
| Read info on whether the mutex is acquired, if it may have waiters |
| and its owner (if any). |
| """ |
| |
| lock_value = self.lock |
| |
| if self.kind & PTHREAD_MUTEX_PRIO_PROTECT_NP: |
| lock_value &= ~(PTHREAD_MUTEX_PRIO_CEILING_MASK) |
| |
| if lock_value == PTHREAD_MUTEX_UNLOCKED: |
| self.values.append(('Status', 'Not acquired')) |
| else: |
| if self.kind & PTHREAD_MUTEX_PRIO_INHERIT_NP: |
| waiters = self.lock & FUTEX_WAITERS |
| owner = self.lock & FUTEX_TID_MASK |
| else: |
| # Mutex protocol is PP or none |
| waiters = (self.lock != PTHREAD_MUTEX_LOCKED_NO_WAITERS) |
| owner = self.owner |
| |
| if waiters: |
| self.values.append(('Status', |
| 'Acquired, possibly with waiters')) |
| else: |
| self.values.append(('Status', |
| 'Acquired, possibly with no waiters')) |
| |
| if self.owner != 0: |
| self.values.append(('Owner ID', owner)) |
| else: |
| # Owner isn't recorded, probably because lock elision |
| # is enabled. |
| self.values.append(('Owner ID', 'Unknown')) |
| |
| def read_attributes(self): |
| """Read the mutex's attributes.""" |
| |
| if self.kind != PTHREAD_MUTEX_DESTROYED: |
| if self.kind & PTHREAD_MUTEX_ROBUST_NORMAL_NP: |
| self.values.append(('Robust', 'Yes')) |
| else: |
| self.values.append(('Robust', 'No')) |
| |
| # In glibc, robust mutexes always have their pshared flag set to |
| # 'shared' regardless of what the pshared flag of their |
| # mutexattr was. Therefore a robust mutex will act as shared |
| # even if it was initialized with a 'private' mutexattr. |
| if self.kind & PTHREAD_MUTEX_PSHARED_BIT: |
| self.values.append(('Shared', 'Yes')) |
| else: |
| self.values.append(('Shared', 'No')) |
| |
| if self.kind & PTHREAD_MUTEX_PRIO_INHERIT_NP: |
| self.values.append(('Protocol', 'Priority inherit')) |
| elif self.kind & PTHREAD_MUTEX_PRIO_PROTECT_NP: |
| prio_ceiling = ((self.lock & PTHREAD_MUTEX_PRIO_CEILING_MASK) |
| >> PTHREAD_MUTEX_PRIO_CEILING_SHIFT) |
| |
| self.values.append(('Protocol', 'Priority protect')) |
| self.values.append(('Priority ceiling', prio_ceiling)) |
| else: |
| # PTHREAD_PRIO_NONE |
| self.values.append(('Protocol', 'None')) |
| |
| def read_misc_info(self): |
| """Read miscellaneous info on the mutex. |
| |
| For now this reads the number of times a recursive mutex was acquired |
| by the same thread. |
| """ |
| |
| mutex_type = self.kind & PTHREAD_MUTEX_KIND_MASK |
| |
| if mutex_type == PTHREAD_MUTEX_RECURSIVE and self.count > 1: |
| self.values.append(('Times acquired by the owner', self.count)) |
| |
| class MutexAttributesPrinter(object): |
| """Pretty printer for pthread_mutexattr_t. |
| |
| In the NPTL this is a type that's always casted to struct pthread_mutexattr |
| which has a single 'mutexkind' field containing the actual attributes. |
| """ |
| |
| def __init__(self, mutexattr): |
| """Initialize the printer's internal data structures. |
| |
| Args: |
| mutexattr: A gdb.value representing a pthread_mutexattr_t. |
| """ |
| |
| self.values = [] |
| |
| try: |
| mutexattr_struct = gdb.lookup_type('struct pthread_mutexattr') |
| self.mutexattr = mutexattr.cast(mutexattr_struct)['mutexkind'] |
| self.read_values() |
| except gdb.error: |
| # libpthread doesn't have debug symbols, thus we can't find the |
| # real struct type. Just print the union members. |
| self.values.append(('__size', mutexattr['__size'])) |
| self.values.append(('__align', mutexattr['__align'])) |
| |
| def to_string(self): |
| """gdb API function. |
| |
| This is called from gdb when we try to print a pthread_mutexattr_t. |
| """ |
| |
| return 'pthread_mutexattr_t' |
| |
| def children(self): |
| """gdb API function. |
| |
| This is called from gdb when we try to print a pthread_mutexattr_t. |
| """ |
| |
| return self.values |
| |
| def read_values(self): |
| """Read the mutexattr's info and store it in self.values. |
| |
| The data contained in self.values will be returned by the Iterator |
| created in self.children. |
| """ |
| |
| mutexattr_type = (self.mutexattr |
| & ~PTHREAD_MUTEXATTR_FLAG_BITS |
| & ~PTHREAD_MUTEX_NO_ELISION_NP) |
| |
| # mutexattr_type must be casted to int because it's a gdb.Value |
| self.values.append(MUTEX_TYPES[int(mutexattr_type)]) |
| |
| if self.mutexattr & PTHREAD_MUTEXATTR_FLAG_ROBUST: |
| self.values.append(('Robust', 'Yes')) |
| else: |
| self.values.append(('Robust', 'No')) |
| |
| if self.mutexattr & PTHREAD_MUTEXATTR_FLAG_PSHARED: |
| self.values.append(('Shared', 'Yes')) |
| else: |
| self.values.append(('Shared', 'No')) |
| |
| protocol = ((self.mutexattr & PTHREAD_MUTEXATTR_PROTOCOL_MASK) >> |
| PTHREAD_MUTEXATTR_PROTOCOL_SHIFT) |
| |
| if protocol == PTHREAD_PRIO_NONE: |
| self.values.append(('Protocol', 'None')) |
| elif protocol == PTHREAD_PRIO_INHERIT: |
| self.values.append(('Protocol', 'Priority inherit')) |
| elif protocol == PTHREAD_PRIO_PROTECT: |
| self.values.append(('Protocol', 'Priority protect')) |
| |
| class ConditionVariablePrinter(object): |
| """Pretty printer for pthread_cond_t.""" |
| |
| def __init__(self, cond): |
| """Initialize the printer's internal data structures. |
| |
| Args: |
| cond: A gdb.value representing a pthread_cond_t. |
| """ |
| |
| data = cond['__data'] |
| self.wrefs = data['__wrefs'] |
| self.values = [] |
| |
| self.read_values() |
| |
| def to_string(self): |
| """gdb API function. |
| |
| This is called from gdb when we try to print a pthread_cond_t. |
| """ |
| |
| return 'pthread_cond_t' |
| |
| def children(self): |
| """gdb API function. |
| |
| This is called from gdb when we try to print a pthread_cond_t. |
| """ |
| |
| return self.values |
| |
| def read_values(self): |
| """Read the condvar's info and store it in self.values. |
| |
| The data contained in self.values will be returned by the Iterator |
| created in self.children. |
| """ |
| |
| self.read_status() |
| self.read_attributes() |
| |
| def read_status(self): |
| """Read the status of the condvar. |
| |
| This method reads whether the condvar is destroyed and how many threads |
| are waiting for it. |
| """ |
| |
| self.values.append(('Threads known to still execute a wait function', |
| self.wrefs >> PTHREAD_COND_WREFS_SHIFT)) |
| |
| def read_attributes(self): |
| """Read the condvar's attributes.""" |
| |
| if (self.wrefs & PTHREAD_COND_CLOCK_MONOTONIC_MASK) != 0: |
| self.values.append(('Clock ID', 'CLOCK_MONOTONIC')) |
| else: |
| self.values.append(('Clock ID', 'CLOCK_REALTIME')) |
| |
| if (self.wrefs & PTHREAD_COND_SHARED_MASK) != 0: |
| self.values.append(('Shared', 'Yes')) |
| else: |
| self.values.append(('Shared', 'No')) |
| |
| class ConditionVariableAttributesPrinter(object): |
| """Pretty printer for pthread_condattr_t. |
| |
| In the NPTL this is a type that's always casted to struct pthread_condattr, |
| which has a single 'value' field containing the actual attributes. |
| """ |
| |
| def __init__(self, condattr): |
| """Initialize the printer's internal data structures. |
| |
| Args: |
| condattr: A gdb.value representing a pthread_condattr_t. |
| """ |
| |
| self.values = [] |
| |
| try: |
| condattr_struct = gdb.lookup_type('struct pthread_condattr') |
| self.condattr = condattr.cast(condattr_struct)['value'] |
| self.read_values() |
| except gdb.error: |
| # libpthread doesn't have debug symbols, thus we can't find the |
| # real struct type. Just print the union members. |
| self.values.append(('__size', condattr['__size'])) |
| self.values.append(('__align', condattr['__align'])) |
| |
| def to_string(self): |
| """gdb API function. |
| |
| This is called from gdb when we try to print a pthread_condattr_t. |
| """ |
| |
| return 'pthread_condattr_t' |
| |
| def children(self): |
| """gdb API function. |
| |
| This is called from gdb when we try to print a pthread_condattr_t. |
| """ |
| |
| return self.values |
| |
| def read_values(self): |
| """Read the condattr's info and store it in self.values. |
| |
| The data contained in self.values will be returned by the Iterator |
| created in self.children. |
| """ |
| |
| clock_id = (self.condattr >> 1) & ((1 << COND_CLOCK_BITS) - 1) |
| |
| if clock_id != 0: |
| self.values.append(('Clock ID', 'CLOCK_MONOTONIC')) |
| else: |
| self.values.append(('Clock ID', 'CLOCK_REALTIME')) |
| |
| if self.condattr & 1: |
| self.values.append(('Shared', 'Yes')) |
| else: |
| self.values.append(('Shared', 'No')) |
| |
| class RWLockPrinter(object): |
| """Pretty printer for pthread_rwlock_t.""" |
| |
| def __init__(self, rwlock): |
| """Initialize the printer's internal data structures. |
| |
| Args: |
| rwlock: A gdb.value representing a pthread_rwlock_t. |
| """ |
| |
| data = rwlock['__data'] |
| self.readers = data['__readers'] |
| self.cur_writer = data['__cur_writer'] |
| self.shared = data['__shared'] |
| self.flags = data['__flags'] |
| self.values = [] |
| self.read_values() |
| |
| def to_string(self): |
| """gdb API function. |
| |
| This is called from gdb when we try to print a pthread_rwlock_t. |
| """ |
| |
| return 'pthread_rwlock_t' |
| |
| def children(self): |
| """gdb API function. |
| |
| This is called from gdb when we try to print a pthread_rwlock_t. |
| """ |
| |
| return self.values |
| |
| def read_values(self): |
| """Read the rwlock's info and store it in self.values. |
| |
| The data contained in self.values will be returned by the Iterator |
| created in self.children. |
| """ |
| |
| self.read_status() |
| self.read_attributes() |
| |
| def read_status(self): |
| """Read the status of the rwlock.""" |
| |
| if self.readers & PTHREAD_RWLOCK_WRPHASE: |
| if self.readers & PTHREAD_RWLOCK_WRLOCKED: |
| self.values.append(('Status', 'Acquired (Write)')) |
| self.values.append(('Writer ID', self.cur_writer)) |
| else: |
| self.values.append(('Status', 'Not acquired')) |
| else: |
| r = self.readers >> PTHREAD_RWLOCK_READER_SHIFT |
| if r > 0: |
| self.values.append(('Status', 'Acquired (Read)')) |
| self.values.append(('Readers', r)) |
| else: |
| self.values.append(('Status', 'Not acquired')) |
| |
| def read_attributes(self): |
| """Read the attributes of the rwlock.""" |
| |
| if self.shared: |
| self.values.append(('Shared', 'Yes')) |
| else: |
| self.values.append(('Shared', 'No')) |
| |
| if self.flags == PTHREAD_RWLOCK_PREFER_READER_NP: |
| self.values.append(('Prefers', 'Readers')) |
| elif self.flags == PTHREAD_RWLOCK_PREFER_WRITER_NP: |
| self.values.append(('Prefers', 'Writers')) |
| else: |
| self.values.append(('Prefers', 'Writers no recursive readers')) |
| |
| class RWLockAttributesPrinter(object): |
| """Pretty printer for pthread_rwlockattr_t. |
| |
| In the NPTL this is a type that's always casted to |
| struct pthread_rwlockattr, which has two fields ('lockkind' and 'pshared') |
| containing the actual attributes. |
| """ |
| |
| def __init__(self, rwlockattr): |
| """Initialize the printer's internal data structures. |
| |
| Args: |
| rwlockattr: A gdb.value representing a pthread_rwlockattr_t. |
| """ |
| |
| self.values = [] |
| |
| try: |
| rwlockattr_struct = gdb.lookup_type('struct pthread_rwlockattr') |
| self.rwlockattr = rwlockattr.cast(rwlockattr_struct) |
| self.read_values() |
| except gdb.error: |
| # libpthread doesn't have debug symbols, thus we can't find the |
| # real struct type. Just print the union members. |
| self.values.append(('__size', rwlockattr['__size'])) |
| self.values.append(('__align', rwlockattr['__align'])) |
| |
| def to_string(self): |
| """gdb API function. |
| |
| This is called from gdb when we try to print a pthread_rwlockattr_t. |
| """ |
| |
| return 'pthread_rwlockattr_t' |
| |
| def children(self): |
| """gdb API function. |
| |
| This is called from gdb when we try to print a pthread_rwlockattr_t. |
| """ |
| |
| return self.values |
| |
| def read_values(self): |
| """Read the rwlockattr's info and store it in self.values. |
| |
| The data contained in self.values will be returned by the Iterator |
| created in self.children. |
| """ |
| |
| rwlock_type = self.rwlockattr['lockkind'] |
| shared = self.rwlockattr['pshared'] |
| |
| if shared == PTHREAD_PROCESS_SHARED: |
| self.values.append(('Shared', 'Yes')) |
| else: |
| # PTHREAD_PROCESS_PRIVATE |
| self.values.append(('Shared', 'No')) |
| |
| if rwlock_type == PTHREAD_RWLOCK_PREFER_READER_NP: |
| self.values.append(('Prefers', 'Readers')) |
| elif rwlock_type == PTHREAD_RWLOCK_PREFER_WRITER_NP: |
| self.values.append(('Prefers', 'Writers')) |
| else: |
| self.values.append(('Prefers', 'Writers no recursive readers')) |
| |
| def register(objfile): |
| """Register the pretty printers within the given objfile.""" |
| |
| printer = gdb.printing.RegexpCollectionPrettyPrinter('glibc-pthread-locks') |
| |
| printer.add_printer('pthread_mutex_t', r'^pthread_mutex_t$', |
| MutexPrinter) |
| printer.add_printer('pthread_mutexattr_t', r'^pthread_mutexattr_t$', |
| MutexAttributesPrinter) |
| printer.add_printer('pthread_cond_t', r'^pthread_cond_t$', |
| ConditionVariablePrinter) |
| printer.add_printer('pthread_condattr_t', r'^pthread_condattr_t$', |
| ConditionVariableAttributesPrinter) |
| printer.add_printer('pthread_rwlock_t', r'^pthread_rwlock_t$', |
| RWLockPrinter) |
| printer.add_printer('pthread_rwlockattr_t', r'^pthread_rwlockattr_t$', |
| RWLockAttributesPrinter) |
| |
| if objfile == None: |
| objfile = gdb |
| |
| gdb.printing.register_pretty_printer(objfile, printer) |
| |
| register(gdb.current_objfile()) |