diff options
-rw-r--r-- | ChangeLog | 27 | ||||
-rw-r--r-- | Makeconfig | 14 | ||||
-rw-r--r-- | Makerules | 46 | ||||
-rw-r--r-- | NEWS | 3 | ||||
-rw-r--r-- | Rules | 5 | ||||
-rw-r--r-- | nptl/Makefile | 1 | ||||
-rw-r--r-- | nptl/nptl-printers.py | 593 | ||||
-rw-r--r-- | nptl/nptl_lock_constants.pysym | 75 | ||||
-rw-r--r-- | pretty-printers/Makefile | 82 | ||||
-rw-r--r-- | pretty-printers/README | 130 | ||||
-rw-r--r-- | pretty-printers/test-condvar-attributes.c | 94 | ||||
-rw-r--r-- | pretty-printers/test-condvar-attributes.py | 60 | ||||
-rw-r--r-- | pretty-printers/test-condvar-printer.c | 57 | ||||
-rw-r--r-- | pretty-printers/test-condvar-printer.py | 45 | ||||
-rw-r--r-- | pretty-printers/test-mutex-attributes.c | 144 | ||||
-rw-r--r-- | pretty-printers/test-mutex-attributes.py | 90 | ||||
-rw-r--r-- | pretty-printers/test-mutex-printer.c | 151 | ||||
-rw-r--r-- | pretty-printers/test-mutex-printer.py | 92 | ||||
-rw-r--r-- | pretty-printers/test-rwlock-attributes.c | 98 | ||||
-rw-r--r-- | pretty-printers/test-rwlock-attributes.py | 62 | ||||
-rw-r--r-- | pretty-printers/test-rwlock-printer.c | 78 | ||||
-rw-r--r-- | pretty-printers/test-rwlock-printer.py | 59 | ||||
-rw-r--r-- | pretty-printers/test_common.py | 315 | ||||
-rw-r--r-- | scripts/gen-py-const.awk | 118 |
24 files changed, 2438 insertions, 1 deletions
@@ -1,3 +1,30 @@ +2016-07-08 Martin Galvan <martin.galvan@tallertechnologies.com> + + * Makeconfig (build-hardcoded-path-in-tests): Set to 'yes' + for shared builds if tests-need-hardcoded-path is defined. + (all-subdirs): Add pretty-printers. + * Makerules ($(py-const)): New rule. + * Rules (others): Add $(py-const), if defined. + * nptl/Makefile (gen-py-const-headers): Define. + * nptl/nptl-printers.py: New file. + * nptl/nptl_lock_constants.pysym: Likewise. + * pretty-printers/Makefile: Likewise. + * pretty-printers/README: Likewise. + * pretty-printers/test-condvar-attributes.c: Likewise. + * pretty-printers/test-condvar-attributes.p: Likewise. + * pretty-printers/test-condvar-printer.c: Likewise. + * pretty-printers/test-condvar-printer.py: Likewise. + * pretty-printers/test-mutex-attributes.c: Likewise. + * pretty-printers/test-mutex-attributes.py: Likewise. + * pretty-printers/test-mutex-printer.c: Likewise. + * pretty-printers/test-mutex-printer.py: Likewise. + * pretty-printers/test-rwlock-attributes.c: Likewise. + * pretty-printers/test-rwlock-attributes.py: Likewise. + * pretty-printers/test-rwlock-printer.c: Likewise. + * pretty-printers/test-rwlock-printer.py: Likewise. + * pretty-printers/test_common.py: Likewise. + * scripts/gen-py-const.awk: Likewise. + 2016-07-08 Chris Metcalf <cmetcalf@mellanox.com> * sysdeps/unix/sysv/linux/tile/kernel-features.h @@ -472,6 +472,18 @@ else +link-tests = $(+link-static-tests) endif endif + +# Some modules may have test programs that must always link against the newly +# built libraries instead of the installed ones. Such modules must define +# tests-need-hardcoded-path in their Makefile before including Makerules. +# This will cause the test programs to be linked with -rpath instead of +# -rpath-link, and their dynamic linker will be set to the built ld.so. +ifeq (yes,$(build-shared)) +ifdef tests-need-hardcoded-path +build-hardcoded-path-in-tests := yes +endif +endif + ifeq (yes,$(build-shared)) ifndef rtld-LDFLAGS rtld-LDFLAGS = -Wl,-dynamic-linker=$(rtlddir)/$(rtld-installed-name) @@ -1097,7 +1109,7 @@ all-subdirs = csu assert ctype locale intl catgets math setjmp signal \ grp pwd posix io termios resource misc socket sysvipc gmon \ gnulib iconv iconvdata wctype manual shadow gshadow po argp \ crypt localedata timezone rt conform debug mathvec \ - $(add-on-subdirs) dlfcn elf + $(add-on-subdirs) dlfcn elf pretty-printers ifndef avoid-generated # sysd-sorted itself will contain rules making the sysd-sorted target @@ -214,6 +214,52 @@ sed-remove-dotdot := -e 's@ *\([^ \/$$][^ \]*\)@ $$(..)\1@g' \ -e 's@^\([^ \/$$][^ \]*\)@$$(..)\1@g' endif +ifdef gen-py-const-headers +# We'll use a static pattern rule to match .pysym files with their +# corresponding generated .py files. +# The generated .py files go in the submodule's dir in the glibc source dir. +py-const-files := $(patsubst %.pysym,%.py,$(gen-py-const-headers)) +py-const-dir := $(objpfx) +py-const := $(addprefix $(py-const-dir),$(py-const-files)) +py-const-script := $(..)scripts/gen-py-const.awk + +# This is a hack we use to generate .py files with constants for Python +# pretty printers. It works the same way as gen-as-const. +# See scripts/gen-py-const.awk for details on how the awk | gcc mechanism +# works. +# +# $@.tmp and $@.tmp2 are temporary files we use to store the partial contents +# of the target file. We do this instead of just writing on $@ because, if the +# build process terminates prematurely, re-running Make wouldn't run this rule +# since Make would see that the target file already exists (despite it being +# incomplete). +# +# The sed line replaces "@name@SOME_NAME@value@SOME_VALUE@" strings from the +# output of 'gcc -S' with "SOME_NAME = SOME_VALUE" strings. +# The '-n' option, combined with the '/p' command, makes sed output only the +# modified lines instead of the whole input file. The output is redirected +# to a .py file; we'll import it in the pretty printers file to read +# the constants generated by gen-py-const.awk. +# The regex has two capturing groups, for SOME_NAME and SOME_VALUE +# respectively. Notice SOME_VALUE may be prepended by a special character, +# depending on the assembly syntax (e.g. immediates are prefixed by a '$' +# in AT&T x86, and by a '#' in ARM). We discard it using a complemented set +# before the second capturing group. +$(py-const): $(py-const-dir)%.py: %.pysym $(py-const-script) \ + $(common-before-compile) + $(make-target-directory) + $(AWK) -f $(py-const-script) $< \ + | $(CC) -S -o $@.tmp $(CFLAGS) $(CPPFLAGS) -x c - + echo '# GENERATED FILE\n' > $@.tmp2 + echo '# Constant definitions for pretty printers.' >> $@.tmp2 + echo '# See gen-py-const.awk for details.\n' >> $@.tmp2 + sed -n -r 's/^.*@name@([^@]+)@value@[^[:xdigit:]Xx-]*([[:xdigit:]Xx-]+)@.*/\1 = \2/p' \ + $@.tmp >> $@.tmp2 + mv -f $@.tmp2 $@ + rm -f $@.tmp + +generated += $(py-const) +endif # gen-py-const-headers ifdef gen-as-const-headers # Generating headers for assembly constants. @@ -47,6 +47,9 @@ Version 2.24 direction of negative infinity. These are currently enabled as GNU extensions. +* Initial support is added for pretty printing of pthread variables in gdb. + See pretty-printers/README for details on how to use it. + Security related changes: * An unnecessary stack copy in _nss_dns_getnetbyname_r was removed. It @@ -98,6 +98,11 @@ tests: $(tests:%=$(objpfx)%.out) $(tests-special) xtests: tests $(xtests:%=$(objpfx)%.out) $(xtests-special) endif +# Generate constant files for Python pretty printers if required. +ifdef py-const +others: $(py-const) +endif + tests-special-notdir = $(patsubst $(objpfx)%, %, $(tests-special)) xtests-special-notdir = $(patsubst $(objpfx)%, %, $(xtests-special)) tests: diff --git a/nptl/Makefile b/nptl/Makefile index 0d8aade..09e09dd 100644 --- a/nptl/Makefile +++ b/nptl/Makefile @@ -307,6 +307,7 @@ gen-as-const-headers = pthread-errnos.sym \ unwindbuf.sym \ lowlevelrobustlock.sym pthread-pi-defines.sym +gen-py-const-headers = nptl_lock_constants.pysym LDFLAGS-pthread.so = -Wl,--enable-new-dtags,-z,nodelete,-z,initfirst diff --git a/nptl/nptl-printers.py b/nptl/nptl-printers.py new file mode 100644 index 0000000..f64216f --- /dev/null +++ b/nptl/nptl-printers.py @@ -0,0 +1,593 @@ +# Pretty printers for the NPTL lock types. +# +# Copyright (C) 2016 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 +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. + + For architectures which support lock elision, this method reads + whether the mutex appears as locked in memory (i.e. it may show it as + unlocked even after calling pthread_mutex_lock). + """ + + 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', 'Unlocked')) + else: + if self.lock & FUTEX_WAITERS: + self.values.append(('Status', 'Locked, possibly with waiters')) + else: + self.values.append(('Status', + 'Locked, 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 locked, 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', 'Unlocked')) + 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', 'Locked, possibly with waiters')) + else: + self.values.append(('Status', + 'Locked, possibly with no waiters')) + + self.values.append(('Owner ID', owner)) + + 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 locked + 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 locked recursively', 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. + """ + + mutexattr_struct = gdb.lookup_type('struct pthread_mutexattr') + self.mutexattr = mutexattr.cast(mutexattr_struct)['mutexkind'] + self.values = [] + self.read_values() + + 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')) + +CLOCK_IDS = { + CLOCK_REALTIME: 'CLOCK_REALTIME', + CLOCK_MONOTONIC: 'CLOCK_MONOTONIC', + CLOCK_PROCESS_CPUTIME_ID: 'CLOCK_PROCESS_CPUTIME_ID', + CLOCK_THREAD_CPUTIME_ID: 'CLOCK_THREAD_CPUTIME_ID', + CLOCK_MONOTONIC_RAW: 'CLOCK_MONOTONIC_RAW', + CLOCK_REALTIME_COARSE: 'CLOCK_REALTIME_COARSE', + CLOCK_MONOTONIC_COARSE: 'CLOCK_MONOTONIC_COARSE' +} + +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.total_seq = data['__total_seq'] + self.mutex = data['__mutex'] + self.nwaiters = data['__nwaiters'] + 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() + self.read_mutex_info() + + 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. + """ + + if self.total_seq == PTHREAD_COND_DESTROYED: + self.values.append(('Status', 'Destroyed')) + + self.values.append(('Threads waiting for this condvar', + self.nwaiters >> COND_NWAITERS_SHIFT)) + + def read_attributes(self): + """Read the condvar's attributes.""" + + clock_id = self.nwaiters & ((1 << COND_NWAITERS_SHIFT) - 1) + + # clock_id must be casted to int because it's a gdb.Value + self.values.append(('Clock ID', CLOCK_IDS[int(clock_id)])) + + shared = (self.mutex == PTHREAD_COND_SHARED) + + if shared: + self.values.append(('Shared', 'Yes')) + else: + self.values.append(('Shared', 'No')) + + def read_mutex_info(self): + """Read the data of the mutex this condvar is bound to. + + A pthread_cond_t's __data.__mutex member is a void * which + must be casted to pthread_mutex_t *. For shared condvars, this + member isn't recorded and has a value of ~0l instead. + """ + + if self.mutex and self.mutex != PTHREAD_COND_SHARED: + mutex_type = gdb.lookup_type('pthread_mutex_t') + mutex = self.mutex.cast(mutex_type.pointer()).dereference() + + self.values.append(('Mutex', mutex)) + +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. + """ + + condattr_struct = gdb.lookup_type('struct pthread_condattr') + self.condattr = condattr.cast(condattr_struct)['value'] + self.values = [] + self.read_values() + + 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 << COND_NWAITERS_SHIFT) - 1) + + # clock_id must be casted to int because it's a gdb.Value + self.values.append(('Clock ID', CLOCK_IDS[int(clock_id)])) + + 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['__nr_readers'] + self.queued_readers = data['__nr_readers_queued'] + self.queued_writers = data['__nr_writers_queued'] + self.writer_id = data['__writer'] + self.shared = data['__shared'] + self.prefers_writers = 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.""" + + # Right now pthread_rwlock_destroy doesn't do anything, so there's no + # way to check if an rwlock is destroyed. + + if self.writer_id: + self.values.append(('Status', 'Locked (Write)')) + self.values.append(('Writer ID', self.writer_id)) + elif self.readers: + self.values.append(('Status', 'Locked (Read)')) + self.values.append(('Readers', self.readers)) + else: + self.values.append(('Status', 'Unlocked')) + + self.values.append(('Queued readers', self.queued_readers)) + self.values.append(('Queued writers', self.queued_writers)) + + 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.prefers_writers: + self.values.append(('Prefers', 'Writers')) + else: + self.values.append(('Prefers', '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. + """ + + rwlockattr_struct = gdb.lookup_type('struct pthread_rwlockattr') + self.rwlockattr = rwlockattr.cast(rwlockattr_struct) + self.values = [] + self.read_values() + + 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 or + rwlock_type == PTHREAD_RWLOCK_PREFER_WRITER_NP): + # This is a known bug. Using PTHREAD_RWLOCK_PREFER_WRITER_NP will + # still make the rwlock prefer readers. + self.values.append(('Prefers', 'Readers')) + elif rwlock_type == PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP: + self.values.append(('Prefers', 'Writers')) + +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) + + gdb.printing.register_pretty_printer(objfile, printer) + +register(gdb.current_objfile()) diff --git a/nptl/nptl_lock_constants.pysym b/nptl/nptl_lock_constants.pysym new file mode 100644 index 0000000..e6ac427 --- /dev/null +++ b/nptl/nptl_lock_constants.pysym @@ -0,0 +1,75 @@ +#include <pthreadP.h> + +-- Mutex types +PTHREAD_MUTEX_KIND_MASK PTHREAD_MUTEX_KIND_MASK_NP +PTHREAD_MUTEX_NORMAL +PTHREAD_MUTEX_RECURSIVE PTHREAD_MUTEX_RECURSIVE_NP +PTHREAD_MUTEX_ERRORCHECK PTHREAD_MUTEX_ERRORCHECK_NP +PTHREAD_MUTEX_ADAPTIVE_NP + +-- Mutex status +-- These are hardcoded all over the code; there are no enums/macros for them. +PTHREAD_MUTEX_DESTROYED -1 +PTHREAD_MUTEX_UNLOCKED 0 +PTHREAD_MUTEX_LOCKED_NO_WAITERS 1 + +-- For robust mutexes +PTHREAD_MUTEX_INCONSISTENT +PTHREAD_MUTEX_NOTRECOVERABLE +FUTEX_OWNER_DIED + +-- For robust and PI mutexes +FUTEX_WAITERS +FUTEX_TID_MASK + +-- Mutex attributes +PTHREAD_MUTEX_ROBUST_NORMAL_NP +PTHREAD_MUTEX_PRIO_INHERIT_NP +PTHREAD_MUTEX_PRIO_PROTECT_NP +PTHREAD_MUTEX_PSHARED_BIT +PTHREAD_MUTEX_PRIO_CEILING_SHIFT +PTHREAD_MUTEX_PRIO_CEILING_MASK + +-- Mutex attribute flags +PTHREAD_MUTEXATTR_PROTOCOL_SHIFT +PTHREAD_MUTEXATTR_PROTOCOL_MASK +PTHREAD_MUTEXATTR_PRIO_CEILING_MASK +PTHREAD_MUTEXATTR_FLAG_ROBUST +PTHREAD_MUTEXATTR_FLAG_PSHARED +PTHREAD_MUTEXATTR_FLAG_BITS +PTHREAD_MUTEX_NO_ELISION_NP + +-- Priority protocols +PTHREAD_PRIO_NONE +PTHREAD_PRIO_INHERIT +PTHREAD_PRIO_PROTECT + +-- These values are hardcoded as well: +-- Value of __mutex for shared condvars. +PTHREAD_COND_SHARED ~0l + +-- Value of __total_seq for destroyed condvars. +PTHREAD_COND_DESTROYED -1ull + +-- __nwaiters encodes the number of threads waiting on a condvar +-- and the clock ID. +-- __nwaiters >> COND_NWAITERS_SHIFT gives us the number of waiters. +COND_NWAITERS_SHIFT + +-- Condvar clock IDs +CLOCK_REALTIME +CLOCK_MONOTONIC +CLOCK_PROCESS_CPUTIME_ID +CLOCK_THREAD_CPUTIME_ID +CLOCK_MONOTONIC_RAW +CLOCK_REALTIME_COARSE +CLOCK_MONOTONIC_COARSE + +-- Rwlock attributes +PTHREAD_RWLOCK_PREFER_READER_NP +PTHREAD_RWLOCK_PREFER_WRITER_NP +PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP + +-- 'Shared' attribute values +PTHREAD_PROCESS_PRIVATE +PTHREAD_PROCESS_SHARED diff --git a/pretty-printers/Makefile b/pretty-printers/Makefile new file mode 100644 index 0000000..8423897 --- /dev/null +++ b/pretty-printers/Makefile @@ -0,0 +1,82 @@ +# Makefile for the Python pretty printers. +# +# Copyright (C) 2016 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 contains rules for building and running the pretty printer tests. + +subdir := pretty-printers +tests-need-hardcoded-path := yes + +include ../Makeconfig + +PYTHON := python + +tests-pretty-printers := test-mutex-attributes test-mutex-printer \ + test-condvar-attributes test-condvar-printer \ + test-rwlock-attributes test-rwlock-printer + +# Add the test programs to test-srcs so that they'll be compiled regardless +# of whether we should actually run them. +test-srcs := $(tests-pretty-printers) + +ifeq ($(build-shared),yes) +nptl-tests-libs := $(shared-thread-library) +else +nptl-tests-libs := $(static-thread-library) +endif + +# The test programs need to be compiled without optimizations so they won't +# confuse gdb. We could use either the 'GCC optimize' pragma or the 'optimize' +# function attribute to achieve this; however, at least on ARM, gcc always +# produces different debugging symbols when invoked with a -O greater than 0 +# than when invoked with -O0, regardless of anything else we're using +# to suppress optimizations. Therefore, we need to explicitly pass -O0 to it +# through CFLAGS. +# Additionally, the build system will try to -include $(common-objpfx)/config.h +# when compiling the tests, which will throw an error if some special macros +# (such as __OPTIMIZE__ and IS_IN_BUILD) aren't defined. To avoid this, we +# tell gcc to define IS_IN_build. +CFLAGS-test-mutex-attributes.c := -O0 -ggdb3 -DIS_IN_build +CFLAGS-test-mutex-printer.c := -O0 -ggdb3 -DIS_IN_build +CFLAGS-test-condvar-attributes.c := -O0 -ggdb3 -DIS_IN_build +CFLAGS-test-condvar-printer.c := -O0 -ggdb3 -DIS_IN_build +CFLAGS-test-rwlock-attributes.c := -O0 -ggdb3 -DIS_IN_build +CFLAGS-test-rwlock-printer.c := -O0 -ggdb3 -DIS_IN_build + +tests-pretty-printers-dest := $(addprefix $(objpfx),$(tests-pretty-printers)) +tests-pretty-printers-pp := $(addsuffix -pp,$(tests-pretty-printers-dest)) + +ifeq ($(run-built-tests),yes) +tests-special += $(tests-pretty-printers-pp) +endif + +include ../Rules + +# Add the thread libraries to the prerequisites of the NPTL test programs. +$(tests-pretty-printers-dest): $(nptl-tests-libs) + +# We won't actually create any *-pp files, so we mark this target as PHONY +# to ensure it always runs when required. +.PHONY: $(tests-pretty-printers-pp) + +# Static pattern rule that matches the test-* targets to their .c and .py +# prerequisites. It'll run the corresponding test script for each test program +# we compiled. test_common.py must be present for all. +$(tests-pretty-printers-pp): $(objpfx)%-pp: $(objpfx)% %.py test_common.py + $(test-wrapper-env) $(PYTHON) $*.py $*.c $(objpfx)$*; \ + $(evaluate-test) diff --git a/pretty-printers/README b/pretty-printers/README new file mode 100644 index 0000000..ff35d7e --- /dev/null +++ b/pretty-printers/README @@ -0,0 +1,130 @@ +README for the glibc Python pretty printers +------------------------------------------- + +Pretty printers are gdb extensions that allow it to print useful, human-readable +information about a program's variables. For example, for a pthread_mutex_t +gdb would usually output something like this: + +(gdb) print mutex +$1 = { + __data = { + __lock = 22020096, + __count = 0, + __owner = 0, + __nusers = 0, + __kind = 576, + __spins = 0, + __elision = 0, + __list = { + __prev = 0x0, + __next = 0x0 + } + }, + __size = "\000\000P\001", '\000' <repeats 12 times>, "@\002", '\000' <repeats 21 times>, + __align = 22020096 +} + +However, with a pretty printer gdb will output something like this: + +(gdb) print mutex +$1 = pthread_mutex_t = { + Type = Normal, + Status = Unlocked, + Robust = No, + Shared = No, + Protocol = Priority protect, + Priority ceiling = 42 +} + +Before printing a value, gdb will first check if there's a pretty printer +registered for it. If there is, it'll use it, otherwise it'll print the value +as usual. Pretty printers can be registered in various ways; for our purposes +we register them for the current objfile by calling +gdb.printing.register_pretty_printer(). + +Currently our printers are based on gdb.RegexpCollectionPrettyPrinter, which +means they'll be triggered if the type of the variable we're printing matches +a given regular expression. For example, MutexPrinter will be triggered if +our variable's type matches the regexp '^pthread_mutex_t$'. + +Besides the printers themselves, each module may have a constants file which the +printers will import. These constants are generated from C headers during the +build process, and need to be in the Python search path when loading the +printers. + + +Installing and loading +---------------------- + +The pretty printers and their constant files may be installed in different paths +for each distro, though gdb should be able to automatically load them by itself. +When in doubt, you can use the 'info pretty printer' gdb command to list the +loaded pretty printers. + +If the printers aren't automatically loaded for some reason, you should add the +following to your .gdbinit: + +python +import sys +sys.path.insert(0, '/path/to/constants/file/directory') +end + +source /path/to/printers.py + +If you're building glibc manually, '/path/to/constants/file/directory' should be +'/path/to/glibc-build/submodule', where 'submodule' is e.g. nptl. + + +Testing +------- + +The pretty printers come with a small test suite based on PExpect, which is a +Python module with Expect-like features for spawning and controlling interactive +programs. Each printer has a corresponding C program and a Python script +that uses PExpect to drive gdb through the program and compare its output to +the expected printer's. + +The tests run on the glibc host, which is assumed to have both gdb and PExpect; +if any of those is absent the tests will fail with code 77 (UNSUPPORTED). +Native builds can be tested simply by doing 'make check'; cross builds must use +cross-test-ssh.sh as test-wrapper, like this: + +make test-wrapper='/path/to/scripts/cross-test-ssh.sh user@host' check + +(Remember to share the build system's filesystem with the glibc host's through +NFS or something similar). + +Running 'make check' on a cross build will only compile the test programs, +without running the scripts. + + +Known issues +------------ + +* Pretty printers are inherently coupled to the code they're targetting, thus +any changes to the target code must also update the corresponding printers. +On the plus side, the printer code itself may serve as a kind of documentation +for the target code. + +* Older versions of the gdb Python API have a bug where +gdb.RegexpCollectionPrettyPrinter would not be able to get a value's real type +if it was typedef'd. This would cause gdb to ignore the pretty printers for +types like pthread_mutex_t, which is defined as: + +typedef union +{ + ... +} pthread_mutex_t; + +This was fixed in commit 1b588015839caafc608a6944a78aea170f5fb2f6. However, +typedef'ing an already typedef'd type may cause a similar issue, e.g.: + +typedef pthread_mutex_t mutex; +mutex a_mutex; + +Here, trying to print a_mutex won't trigger the pthread_mutex_t printer. + +* The test programs must be compiled without optimizations. This is necessary +because the test scripts rely on the C code structure being preserved when +stepping through the programs. Things like aggressive instruction reordering +or optimizing variables out may make this kind of testing impossible. diff --git a/pretty-printers/test-condvar-attributes.c b/pretty-printers/test-condvar-attributes.c new file mode 100644 index 0000000..4db4098 --- /dev/null +++ b/pretty-printers/test-condvar-attributes.c @@ -0,0 +1,94 @@ +/* Helper program for testing the pthread_cond_t and pthread_condattr_t + pretty printers. + + Copyright (C) 2016 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/>. */ + +/* Keep the calls to the pthread_* functions on separate lines to make it easy + to advance through the program using the gdb 'next' command. */ + +#include <time.h> +#include <pthread.h> + +#define PASS 0 +#define FAIL 1 + +static int condvar_reinit (pthread_cond_t *condvar, + const pthread_condattr_t *attr); +static int test_setclock (pthread_cond_t *condvar, pthread_condattr_t *attr); +static int test_setpshared (pthread_cond_t *condvar, pthread_condattr_t *attr); + +/* Need these so we don't have lines longer than 79 chars. */ +#define SET_SHARED(attr, shared) pthread_condattr_setpshared (attr, shared) + +int +main (void) +{ + pthread_cond_t condvar; + pthread_condattr_t attr; + int result = FAIL; + + if (pthread_condattr_init (&attr) == 0 + && pthread_cond_init (&condvar, NULL) == 0 + && test_setclock (&condvar, &attr) == PASS + && test_setpshared (&condvar, &attr) == PASS) + result = PASS; + /* Else, one of the pthread_cond* functions failed. */ + + return result; +} + +/* Destroys CONDVAR and re-initializes it using ATTR. */ +static int +condvar_reinit (pthread_cond_t *condvar, const pthread_condattr_t *attr) +{ + int result = FAIL; + + if (pthread_cond_destroy (condvar) == 0 + && pthread_cond_init (condvar, attr) == 0) + result = PASS; + + return result; +} + +/* Tests setting the clock ID attribute. */ +static int +test_setclock (pthread_cond_t *condvar, pthread_condattr_t *attr) +{ + int result = FAIL; + + if (pthread_condattr_setclock (attr, CLOCK_REALTIME) == 0 /* Set clock. */ + && condvar_reinit (condvar, attr) == PASS) + result = PASS; + + return result; +} + +/* Tests setting whether the condvar can be shared between processes. */ +static int +test_setpshared (pthread_cond_t *condvar, pthread_condattr_t *attr) +{ + int result = FAIL; + + if (SET_SHARED (attr, PTHREAD_PROCESS_SHARED) == 0 /* Set shared. */ + && condvar_reinit (condvar, attr) == PASS + && SET_SHARED (attr, PTHREAD_PROCESS_PRIVATE) == 0 + && condvar_reinit (condvar, attr) == PASS) + result = PASS; + + return result; +} diff --git a/pretty-printers/test-condvar-attributes.py b/pretty-printers/test-condvar-attributes.py new file mode 100644 index 0000000..896feab --- /dev/null +++ b/pretty-printers/test-condvar-attributes.py @@ -0,0 +1,60 @@ +# Common tests for the ConditionVariablePrinter and +# ConditionVariableAttributesPrinter classes. +# +# Copyright (C) 2016 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/>. + +import sys + +from test_common import * + +test_source = sys.argv[1] +test_bin = sys.argv[2] +result = PASS + +try: + init_test(test_bin) + go_to_main() + + condvar_var = 'condvar' + condvar_to_string = 'pthread_cond_t' + + attr_var = 'attr' + attr_to_string = 'pthread_condattr_t' + + break_at(test_source, 'Set clock') + continue_cmd() # Go to test_setclock + next_cmd(2) + test_printer(condvar_var, condvar_to_string, {'Clock ID': 'CLOCK_REALTIME'}) + test_printer(attr_var, attr_to_string, {'Clock ID': 'CLOCK_REALTIME'}) + + break_at(test_source, 'Set shared') + continue_cmd() # Go to test_setpshared + next_cmd(2) + test_printer(condvar_var, condvar_to_string, {'Shared': 'Yes'}) + test_printer(attr_var, attr_to_string, {'Shared': 'Yes'}) + next_cmd(2) + test_printer(condvar_var, condvar_to_string, {'Shared': 'No'}) + test_printer(attr_var, attr_to_string, {'Shared': 'No'}) + + continue_cmd() # Exit + +except (NoLineError, pexpect.TIMEOUT) as exception: + print('Error: {0}'.format(exception)) + result = FAIL + +exit(result) diff --git a/pretty-printers/test-condvar-printer.c b/pretty-printers/test-condvar-printer.c new file mode 100644 index 0000000..0f2a5f4 --- /dev/null +++ b/pretty-printers/test-condvar-printer.c @@ -0,0 +1,57 @@ +/* Helper program for testing the pthread_cond_t pretty printer. + + Copyright (C) 2016 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/>. */ + +/* Keep the calls to the pthread_* functions on separate lines to make it easy + to advance through the program using the gdb 'next' command. */ + +#include <time.h> +#include <pthread.h> + +#define PASS 0 +#define FAIL 1 + +static int test_status_destroyed (pthread_cond_t *condvar); + +int +main (void) +{ + pthread_cond_t condvar; + pthread_condattr_t attr; + int result = FAIL; + + if (pthread_condattr_init (&attr) == 0 + && test_status_destroyed (&condvar) == PASS) + result = PASS; + /* Else, one of the pthread_cond* functions failed. */ + + return result; +} + +/* Initializes CONDVAR, then destroys it. */ +static int +test_status_destroyed (pthread_cond_t *condvar) +{ + int result = FAIL; + + if (pthread_cond_init (condvar, NULL) == 0 + && pthread_cond_destroy (condvar) == 0) + result = PASS; /* Test status (destroyed). */ + + return result; +} diff --git a/pretty-printers/test-condvar-printer.py b/pretty-printers/test-condvar-printer.py new file mode 100644 index 0000000..939bbf4 --- /dev/null +++ b/pretty-printers/test-condvar-printer.py @@ -0,0 +1,45 @@ +# Common tests for the ConditionVariablePrinter class. +# +# Copyright (C) 2016 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/>. + +import sys + +from test_common import * + +test_source = sys.argv[1] +test_bin = sys.argv[2] +result = PASS + +try: + init_test(test_bin) + go_to_main() + + var = 'condvar' + to_string = 'pthread_cond_t' + + break_at(test_source, 'Test status (destroyed)') + continue_cmd() # Go to test_status_destroyed + test_printer(var, to_string, {'Status': 'Destroyed'}) + + continue_cmd() # Exit + +except (NoLineError, pexpect.TIMEOUT) as exception: + print('Error: {0}'.format(exception)) + result = FAIL + +exit(result) diff --git a/pretty-printers/test-mutex-attributes.c b/pretty-printers/test-mutex-attributes.c new file mode 100644 index 0000000..9ecfff7 --- /dev/null +++ b/pretty-printers/test-mutex-attributes.c @@ -0,0 +1,144 @@ +/* Helper program for testing the pthread_mutex_t and pthread_mutexattr_t + pretty printers. + + Copyright (C) 2016 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/>. */ + +/* Keep the calls to the pthread_* functions on separate lines to make it easy + to advance through the program using the gdb 'next' command. */ + +#include <pthread.h> + +#define PASS 0 +#define FAIL 1 +#define PRIOCEILING 42 + +/* Need these so we don't have lines longer than 79 chars. */ +#define SET_TYPE(attr, type) pthread_mutexattr_settype (attr, type) +#define SET_ROBUST(attr, robust) pthread_mutexattr_setrobust (attr, robust) +#define SET_SHARED(attr, shared) pthread_mutexattr_setpshared (attr, shared) +#define SET_PROTOCOL(attr, protocol) \ + pthread_mutexattr_setprotocol (attr, protocol) +#define SET_PRIOCEILING(mutex, prioceiling, old_ceiling) \ + pthread_mutex_setprioceiling (mutex, prioceiling, old_ceiling) + +static int mutex_reinit (pthread_mutex_t *mutex, + const pthread_mutexattr_t *attr); +static int test_settype (pthread_mutex_t *mutex, pthread_mutexattr_t *attr); +static int test_setrobust (pthread_mutex_t *mutex, pthread_mutexattr_t *attr); +static int test_setpshared (pthread_mutex_t *mutex, pthread_mutexattr_t *attr); +static int test_setprotocol (pthread_mutex_t *mutex, + pthread_mutexattr_t *attr); + +int +main (void) +{ + pthread_mutex_t mutex; + pthread_mutexattr_t attr; + int result = FAIL; + + if (pthread_mutexattr_init (&attr) == 0 + && pthread_mutex_init (&mutex, NULL) == 0 + && test_settype (&mutex, &attr) == PASS + && test_setrobust (&mutex, &attr) == PASS + && test_setpshared (&mutex, &attr) == PASS + && test_setprotocol (&mutex, &attr) == PASS) + result = PASS; + /* Else, one of the pthread_mutex* functions failed. */ + + return result; +} + +/* Destroys MUTEX and re-initializes it using ATTR. */ +static int +mutex_reinit (pthread_mutex_t *mutex, const pthread_mutexattr_t *attr) +{ + int result = FAIL; + + if (pthread_mutex_destroy (mutex) == 0 + && pthread_mutex_init (mutex, attr) == 0) + result = PASS; + + return result; +} + +/* Tests setting the mutex type. */ +static int +test_settype (pthread_mutex_t *mutex, pthread_mutexattr_t *attr) +{ + int result = FAIL; + + if (SET_TYPE (attr, PTHREAD_MUTEX_ERRORCHECK) == 0 /* Set type. */ + && mutex_reinit (mutex, attr) == 0 + && SET_TYPE (attr, PTHREAD_MUTEX_RECURSIVE) == 0 + && mutex_reinit (mutex, attr) == 0 + && SET_TYPE (attr, PTHREAD_MUTEX_NORMAL) == 0 + && mutex_reinit (mutex, attr) == 0) + result = PASS; + + return result; +} + +/* Tests setting whether the mutex is robust. */ +static int +test_setrobust (pthread_mutex_t *mutex, pthread_mutexattr_t *attr) +{ + int result = FAIL; + + if (SET_ROBUST (attr, PTHREAD_MUTEX_ROBUST) == 0 /* Set robust. */ + && mutex_reinit (mutex, attr) == 0 + && SET_ROBUST (attr, PTHREAD_MUTEX_STALLED) == 0 + && mutex_reinit (mutex, attr) == 0) + result = PASS; + + return result; +} + +/* Tests setting whether the mutex can be shared between processes. */ +static int +test_setpshared (pthread_mutex_t *mutex, pthread_mutexattr_t *attr) +{ + int result = FAIL; + + if (SET_SHARED (attr, PTHREAD_PROCESS_SHARED) == 0 /* Set shared. */ + && mutex_reinit (mutex, attr) == 0 + && SET_SHARED (attr, PTHREAD_PROCESS_PRIVATE) == 0 + && mutex_reinit (mutex, attr) == 0) + result = PASS; + + return result; +} + +/* Tests setting the mutex protocol and, for Priority Protect, the Priority + Ceiling. */ +static int +test_setprotocol (pthread_mutex_t *mutex, pthread_mutexattr_t *attr) +{ + int result = FAIL; + int old_prioceiling; + + if (SET_PROTOCOL (attr, PTHREAD_PRIO_INHERIT) == 0 /* Set protocol. */ + && mutex_reinit (mutex, attr) == 0 + && SET_PROTOCOL (attr, PTHREAD_PRIO_PROTECT) == 0 + && mutex_reinit (mutex, attr) == 0 + && SET_PRIOCEILING(mutex, PRIOCEILING, &old_prioceiling) == 0 + && SET_PROTOCOL (attr, PTHREAD_PRIO_NONE) == 0 + && mutex_reinit (mutex, attr) == 0) + result = PASS; + + return result; +} diff --git a/pretty-printers/test-mutex-attributes.py b/pretty-printers/test-mutex-attributes.py new file mode 100644 index 0000000..40883ad --- /dev/null +++ b/pretty-printers/test-mutex-attributes.py @@ -0,0 +1,90 @@ +# Common tests for the MutexPrinter and MutexAttributesPrinter classes. +# +# Copyright (C) 2016 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/>. + +import sys + +from test_common import * + +test_source = sys.argv[1] +test_bin = sys.argv[2] +result = PASS +PRIOCEILING = 42 + +try: + init_test(test_bin) + go_to_main() + + mutex_var = 'mutex' + mutex_to_string = 'pthread_mutex_t' + + attr_var = 'attr' + attr_to_string = 'pthread_mutexattr_t' + + break_at(test_source, 'Set type') + continue_cmd() # Go to test_settype + next_cmd(2) + test_printer(attr_var, attr_to_string, {'Type': 'Error check'}) + test_printer(mutex_var, mutex_to_string, {'Type': 'Error check'}) + next_cmd(2) + test_printer(attr_var, attr_to_string, {'Type': 'Recursive'}) + test_printer(mutex_var, mutex_to_string, {'Type': 'Recursive'}) + next_cmd(2) + test_printer(attr_var, attr_to_string, {'Type': 'Normal'}) + test_printer(mutex_var, mutex_to_string, {'Type': 'Normal'}) + + break_at(test_source, 'Set robust') + continue_cmd() # Go to test_setrobust + next_cmd(2) + test_printer(attr_var, attr_to_string, {'Robust': 'Yes'}) + test_printer(mutex_var, mutex_to_string, {'Robust': 'Yes'}) + next_cmd(2) + test_printer(attr_var, attr_to_string, {'Robust': 'No'}) + test_printer(mutex_var, mutex_to_string, {'Robust': 'No'}) + + break_at(test_source, 'Set shared') + continue_cmd() # Go to test_setpshared + next_cmd(2) + test_printer(attr_var, attr_to_string, {'Shared': 'Yes'}) + test_printer(mutex_var, mutex_to_string, {'Shared': 'Yes'}) + next_cmd(2) + test_printer(attr_var, attr_to_string, {'Shared': 'No'}) + test_printer(mutex_var, mutex_to_string, {'Shared': 'No'}) + + break_at(test_source, 'Set protocol') + continue_cmd() # Go to test_setprotocol + next_cmd(2) + test_printer(attr_var, attr_to_string, {'Protocol': 'Priority inherit'}) + test_printer(mutex_var, mutex_to_string, {'Protocol': 'Priority inherit'}) + next_cmd(2) + test_printer(attr_var, attr_to_string, {'Protocol': 'Priority protect'}) + test_printer(mutex_var, mutex_to_string, {'Protocol': 'Priority protect'}) + next_cmd(2) + test_printer(mutex_var, mutex_to_string, {'Priority ceiling': + str(PRIOCEILING)}) + next_cmd() + test_printer(attr_var, attr_to_string, {'Protocol': 'None'}) + test_printer(mutex_var, mutex_to_string, {'Protocol': 'None'}) + + continue_cmd() # Exit + +except (NoLineError, pexpect.TIMEOUT) as exception: + print('Error: {0}'.format(exception)) + result = FAIL + +exit(result) diff --git a/pretty-printers/test-mutex-printer.c b/pretty-printers/test-mutex-printer.c new file mode 100644 index 0000000..b973e82 --- /dev/null +++ b/pretty-printers/test-mutex-printer.c @@ -0,0 +1,151 @@ +/* Helper program for testing the pthread_mutex_t pretty printer. + + Copyright (C) 2016 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/>. */ + +/* Keep the calls to the pthread_* functions on separate lines to make it easy + to advance through the program using the gdb 'next' command. */ + +#include <stdlib.h> +#include <errno.h> +#include <pthread.h> + +#define PASS 0 +#define FAIL 1 + +static int test_status_destroyed (pthread_mutex_t *mutex); +static int test_status_no_robust (pthread_mutex_t *mutex, + pthread_mutexattr_t *attr); +static int test_status_robust (pthread_mutex_t *mutex, + pthread_mutexattr_t *attr); +static int test_locking_state_robust (pthread_mutex_t *mutex); +static void *thread_func (void *arg); +static int test_recursive_locks (pthread_mutex_t *mutex, + pthread_mutexattr_t *attr); + +int +main (void) +{ + pthread_mutex_t mutex; + pthread_mutexattr_t attr; + int result = FAIL; + + if (pthread_mutexattr_init (&attr) == 0 + && test_status_destroyed (&mutex) == PASS + && test_status_no_robust (&mutex, &attr) == PASS + && test_status_robust (&mutex, &attr) == PASS + && test_recursive_locks (&mutex, &attr) == PASS) + result = PASS; + /* Else, one of the pthread_mutex* functions failed. */ + + return result; +} + +/* Initializes MUTEX, then destroys it. */ +static int +test_status_destroyed (pthread_mutex_t *mutex) +{ + int result = FAIL; + + if (pthread_mutex_init (mutex, NULL) == 0 + && pthread_mutex_destroy (mutex) == 0) + result = PASS; /* Test status (destroyed). */ + + return result; +} + +/* Tests locking of non-robust mutexes. */ +static int +test_status_no_robust (pthread_mutex_t *mutex, pthread_mutexattr_t *attr) +{ + int result = FAIL; + + if (pthread_mutexattr_setrobust (attr, PTHREAD_MUTEX_STALLED) == 0 + && pthread_mutex_init (mutex, attr) == 0 + && pthread_mutex_lock (mutex) == 0 /* Test status (non-robust). */ + && pthread_mutex_unlock (mutex) == 0 + && pthread_mutex_destroy (mutex) == 0) + result = PASS; + + return result; +} + +/* Tests locking of robust mutexes. */ +static int +test_status_robust (pthread_mutex_t *mutex, pthread_mutexattr_t *attr) +{ + int result = FAIL; + + if (pthread_mutexattr_setrobust (attr, PTHREAD_MUTEX_ROBUST) == 0 + && pthread_mutex_init (mutex, attr) == 0 + && test_locking_state_robust (mutex) == PASS /* Test status (robust). */ + && pthread_mutex_destroy (mutex) == 0) + result = PASS; + + return result; +} + +/* Tests locking and state corruption of robust mutexes. We'll mark it as + inconsistent, then not recoverable. */ +static int +test_locking_state_robust (pthread_mutex_t *mutex) +{ + int result = FAIL; + pthread_t thread; + + if (pthread_create (&thread, NULL, thread_func, mutex) == 0 /* Create. */ + && pthread_join (thread, NULL) == 0 + && pthread_mutex_lock (mutex) == EOWNERDEAD /* Test locking (robust). */ + && pthread_mutex_unlock (mutex) == 0) + result = PASS; + + return result; +} + +/* Function to be called by the child thread when testing robust mutexes. */ +static void * +thread_func (void *arg) +{ + pthread_mutex_t *mutex = (pthread_mutex_t *)arg; + + if (pthread_mutex_lock (mutex) != 0) /* Thread function. */ + exit (FAIL); + + /* Thread terminates without unlocking the mutex, thus marking it as + inconsistent. */ + return NULL; +} + +/* Tests locking the mutex multiple times in a row. */ +static int +test_recursive_locks (pthread_mutex_t *mutex, pthread_mutexattr_t *attr) +{ + int result = FAIL; + + if (pthread_mutexattr_settype (attr, PTHREAD_MUTEX_RECURSIVE) == 0 + && pthread_mutex_init (mutex, attr) == 0 + && pthread_mutex_lock (mutex) == 0 + && pthread_mutex_lock (mutex) == 0 + && pthread_mutex_lock (mutex) == 0 /* Test recursive locks. */ + && pthread_mutex_unlock (mutex) == 0 + && pthread_mutex_unlock (mutex) == 0 + && pthread_mutex_unlock (mutex) == 0 + && pthread_mutex_destroy (mutex) == 0) + result = PASS; + + return result; +} diff --git a/pretty-printers/test-mutex-printer.py b/pretty-printers/test-mutex-printer.py new file mode 100644 index 0000000..44f4a48 --- /dev/null +++ b/pretty-printers/test-mutex-printer.py @@ -0,0 +1,92 @@ +# Tests for the MutexPrinter class. +# +# Copyright (C) 2016 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/>. + +import sys + +from test_common import * + +test_source = sys.argv[1] +test_bin = sys.argv[2] +result = PASS + +try: + init_test(test_bin) + go_to_main() + + var = 'mutex' + to_string = 'pthread_mutex_t' + + break_at(test_source, 'Test status (destroyed)') + continue_cmd() # Go to test_status_destroyed + test_printer(var, to_string, {'Status': 'Destroyed'}) + + break_at(test_source, 'Test status (non-robust)') + continue_cmd() # Go to test_status_no_robust + test_printer(var, to_string, {'Status': 'Unlocked'}) + next_cmd() + thread_id = get_current_thread_lwpid() + test_printer(var, to_string, {'Status': 'Locked, possibly with no waiters', + 'Owner ID': thread_id}) + + break_at(test_source, 'Test status (robust)') + continue_cmd() # Go to test_status_robust + test_printer(var, to_string, {'Status': 'Unlocked'}) + + # We'll now test the robust mutex locking states. We'll create a new + # thread that will lock a robust mutex and exit without unlocking it. + break_at(test_source, 'Create') + continue_cmd() # Go to test_locking_state_robust + # Set a breakpoint for the new thread to hit. + break_at(test_source, 'Thread function') + continue_cmd() + # By now the new thread is created and has hit its breakpoint. + set_scheduler_locking(True) + parent = '1' + child = '2' + select_thread(child) + child_id = get_current_thread_lwpid() + # We've got the new thread's ID. + select_thread(parent) + # Make the new thread finish its function while we wait. + continue_cmd(thread=child) + # The new thread should be dead by now. + break_at(test_source, 'Test locking (robust)') + continue_cmd() + test_printer(var, to_string, {'Owner ID': r'{0} \(dead\)'.format(child_id)}) + # Try to lock and unlock the mutex. + next_cmd() + test_printer(var, to_string, {'Owner ID': thread_id, + 'State protected by this mutex': 'Inconsistent'}) + next_cmd() + test_printer(var, to_string, {'Status': 'Unlocked', + 'State protected by this mutex': 'Not recoverable'}) + set_scheduler_locking(False) + + break_at(test_source, 'Test recursive locks') + continue_cmd() # Go to test_recursive_locks + test_printer(var, to_string, {'Times locked recursively': '2'}) + next_cmd() + test_printer(var, to_string, {'Times locked recursively': '3'}) + continue_cmd() # Exit + +except (NoLineError, pexpect.TIMEOUT) as exception: + print('Error: {0}'.format(exception)) + result = FAIL + +exit(result) diff --git a/pretty-printers/test-rwlock-attributes.c b/pretty-printers/test-rwlock-attributes.c new file mode 100644 index 0000000..d12facf --- /dev/null +++ b/pretty-printers/test-rwlock-attributes.c @@ -0,0 +1,98 @@ +/* Helper program for testing the pthread_rwlock_t and pthread_rwlockattr_t + pretty printers. + + Copyright (C) 2016 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/>. */ + +/* Keep the calls to the pthread_* functions on separate lines to make it easy + to advance through the program using the gdb 'next' command. */ + +#include <pthread.h> + +#define PASS 0 +#define FAIL 1 + +/* Need these so we don't have lines longer than 79 chars. */ +#define SET_KIND(attr, kind) pthread_rwlockattr_setkind_np (attr, kind) +#define SET_SHARED(attr, shared) pthread_rwlockattr_setpshared (attr, shared) + +static int rwlock_reinit (pthread_rwlock_t *rwlock, + const pthread_rwlockattr_t *attr); +static int test_setkind_np (pthread_rwlock_t *rwlock, + pthread_rwlockattr_t *attr); +static int test_setpshared (pthread_rwlock_t *rwlock, + pthread_rwlockattr_t *attr); + +int +main (void) +{ + pthread_rwlock_t rwlock; + pthread_rwlockattr_t attr; + int result = FAIL; + + if (pthread_rwlockattr_init (&attr) == 0 + && pthread_rwlock_init (&rwlock, NULL) == 0 + && test_setkind_np (&rwlock, &attr) == PASS + && test_setpshared (&rwlock, &attr) == PASS) + result = PASS; + /* Else, one of the pthread_rwlock* functions failed. */ + + return result; +} + +/* Destroys RWLOCK and re-initializes it using ATTR. */ +static int +rwlock_reinit (pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attr) +{ + int result = FAIL; + + if (pthread_rwlock_destroy (rwlock) == 0 + && pthread_rwlock_init (rwlock, attr) == 0) + result = PASS; + + return result; +} + +/* Tests setting whether the rwlock prefers readers or writers. */ +static int +test_setkind_np (pthread_rwlock_t *rwlock, pthread_rwlockattr_t *attr) +{ + int result = FAIL; + + if (SET_KIND (attr, PTHREAD_RWLOCK_PREFER_READER_NP) == 0 /* Set kind. */ + && rwlock_reinit (rwlock, attr) == PASS + && SET_KIND (attr, PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP) == 0 + && rwlock_reinit (rwlock, attr) == PASS) + result = PASS; + + return result; +} + +/* Tests setting whether the rwlock can be shared between processes. */ +static int +test_setpshared (pthread_rwlock_t *rwlock, pthread_rwlockattr_t *attr) +{ + int result = FAIL; + + if (SET_SHARED (attr, PTHREAD_PROCESS_SHARED) == 0 /* Set shared. */ + && rwlock_reinit (rwlock, attr) == PASS + && SET_SHARED (attr, PTHREAD_PROCESS_PRIVATE) == 0 + && rwlock_reinit (rwlock, attr) == PASS) + result = PASS; + + return result; +} diff --git a/pretty-printers/test-rwlock-attributes.py b/pretty-printers/test-rwlock-attributes.py new file mode 100644 index 0000000..7655d3d --- /dev/null +++ b/pretty-printers/test-rwlock-attributes.py @@ -0,0 +1,62 @@ +# Common tests for the RWLockPrinter and RWLockAttributesPrinter classes. +# +# Copyright (C) 2016 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/>. + +import sys + +from test_common import * + +test_source = sys.argv[1] +test_bin = sys.argv[2] +result = PASS + +try: + init_test(test_bin) + go_to_main() + + rwlock_var = 'rwlock' + rwlock_to_string = 'pthread_rwlock_t' + + attr_var = 'attr' + attr_to_string = 'pthread_rwlockattr_t' + + break_at(test_source, 'Set kind') + continue_cmd() # Go to test_setkind_np + next_cmd(2) + test_printer(rwlock_var, rwlock_to_string, {'Prefers': 'Readers'}) + test_printer(attr_var, attr_to_string, {'Prefers': 'Readers'}) + next_cmd(2) + test_printer(rwlock_var, rwlock_to_string, {'Prefers': 'Writers'}) + test_printer(attr_var, attr_to_string, {'Prefers': 'Writers'}) + + break_at(test_source, 'Set shared') + continue_cmd() # Go to test_setpshared + next_cmd(2) + test_printer(rwlock_var, rwlock_to_string, {'Shared': 'Yes'}) + test_printer(attr_var, attr_to_string, {'Shared': 'Yes'}) + next_cmd(2) + test_printer(rwlock_var, rwlock_to_string, {'Shared': 'No'}) + test_printer(attr_var, attr_to_string, {'Shared': 'No'}) + + continue_cmd() # Exit + +except (NoLineError, pexpect.TIMEOUT) as exception: + print('Error: {0}'.format(exception)) + result = FAIL + +exit(result) diff --git a/pretty-printers/test-rwlock-printer.c b/pretty-printers/test-rwlock-printer.c new file mode 100644 index 0000000..dbbe9b8 --- /dev/null +++ b/pretty-printers/test-rwlock-printer.c @@ -0,0 +1,78 @@ +/* Helper program for testing the pthread_rwlock_t pretty printer. + + Copyright (C) 2016 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/>. */ + +/* Keep the calls to the pthread_* functions on separate lines to make it easy + to advance through the program using the gdb 'next' command. */ + +#include <pthread.h> + +#define PASS 0 +#define FAIL 1 + +static int test_locking_reader (pthread_rwlock_t *rwlock); +static int test_locking_writer (pthread_rwlock_t *rwlock); + +int +main (void) +{ + pthread_rwlock_t rwlock; + + int result = FAIL; + + if (test_locking_reader (&rwlock) == PASS + && test_locking_writer (&rwlock) == PASS) + result = PASS; + /* Else, one of the pthread_rwlock* functions failed. */ + + return result; +} + +/* Tests locking the rwlock multiple times as a reader. */ +static int +test_locking_reader (pthread_rwlock_t *rwlock) +{ + int result = FAIL; + + if (pthread_rwlock_init (rwlock, NULL) == 0 + && pthread_rwlock_rdlock (rwlock) == 0 /* Test locking (reader). */ + && pthread_rwlock_rdlock (rwlock) == 0 + && pthread_rwlock_rdlock (rwlock) == 0 + && pthread_rwlock_unlock (rwlock) == 0 + && pthread_rwlock_unlock (rwlock) == 0 + && pthread_rwlock_unlock (rwlock) == 0 + && pthread_rwlock_destroy (rwlock) == 0) + result = PASS; + + return result; +} + +/* Tests locking the rwlock as a writer. */ +static int +test_locking_writer (pthread_rwlock_t *rwlock) +{ + int result = FAIL; + + if (pthread_rwlock_init (rwlock, NULL) == 0 + && pthread_rwlock_wrlock (rwlock) == 0 /* Test locking (writer). */ + && pthread_rwlock_unlock (rwlock) == 0 + && pthread_rwlock_destroy (rwlock) == 0) + result = PASS; + + return result; +} diff --git a/pretty-printers/test-rwlock-printer.py b/pretty-printers/test-rwlock-printer.py new file mode 100644 index 0000000..8820321 --- /dev/null +++ b/pretty-printers/test-rwlock-printer.py @@ -0,0 +1,59 @@ +# Common tests for the RWLockPrinter class. +# +# Copyright (C) 2016 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/>. + +import sys + +from test_common import * + +test_source = sys.argv[1] +test_bin = sys.argv[2] +result = PASS + +try: + init_test(test_bin) + go_to_main() + + var = 'rwlock' + to_string = 'pthread_rwlock_t' + + break_at(test_source, 'Test locking (reader)') + continue_cmd() # Go to test_locking_reader + test_printer(var, to_string, {'Status': 'Unlocked'}) + next_cmd() + test_printer(var, to_string, {'Status': r'Locked \(Read\)', 'Readers': '1'}) + next_cmd() + test_printer(var, to_string, {'Readers': '2'}) + next_cmd() + test_printer(var, to_string, {'Readers': '3'}) + + break_at(test_source, 'Test locking (writer)') + continue_cmd() # Go to test_locking_writer + test_printer(var, to_string, {'Status': 'Unlocked'}) + next_cmd() + thread_id = get_current_thread_lwpid() + test_printer(var, to_string, {'Status': r'Locked \(Write\)', + 'Writer ID': thread_id}) + + continue_cmd() # Exit + +except (NoLineError, pexpect.TIMEOUT) as exception: + print('Error: {0}'.format(exception)) + result = FAIL + +exit(result) diff --git a/pretty-printers/test_common.py b/pretty-printers/test_common.py new file mode 100644 index 0000000..de758f8 --- /dev/null +++ b/pretty-printers/test_common.py @@ -0,0 +1,315 @@ +# Common functions and variables for testing the Python pretty printers. +# +# Copyright (C) 2016 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/>. + +"""These tests require PExpect. + +Attributes: + PASS, FAIL, UNSUPPORTED (int): Test exit codes, as per evaluate-test.sh. + GDB (string): A string with the name of the gdb binary. + gdb (pexpect.spawn): The gdb process, as handled by PExpect. + gdb_prompt (raw string): A pattern for matching the gdb prompt. +""" + +import os +import re + +PASS = 0 +FAIL = 1 +UNSUPPORTED = 77 +GDB = 'gdb' + +try: + import pexpect +except ImportError: + print('PExpect must be installed in order to test the pretty printers.') + exit(UNSUPPORTED) + +if not pexpect.which(GDB): + print('gdb must be installed in order to test the pretty printers.') + exit(UNSUPPORTED) + +class NoLineError(Exception): + """Custom exception which indicates that a test file doesn't contain + the requested string. + """ + + def __init__(self, file_name, string): + """Constructor. + + Args: + file_name (string): The name of the test file. + string (string): The string that was requested. + """ + + super(NoLineError, self).__init__() + self.file_name = file_name + self.string = string + + def __str__(self): + """Shows a readable representation of the exception.""" + + return ('File {0} has no line containing the following string: {1}' + .format(self.file_name, self.string)) + +timeout = 1 +TIMEOUTFACTOR = os.environ.get('TIMEOUTFACTOR') + +if TIMEOUTFACTOR: + timeout = int(TIMEOUTFACTOR) + +gdb = pexpect.spawn(GDB, echo=False, timeout=timeout) + +# Set the gdb prompt to a custom one, so that user-defined prompts won't +# interfere. We assume the user won't have his prompt set to this. +gdb_prompt = r'gdb-test% ' +gdb.sendline('set prompt {0}'.format(gdb_prompt)) +gdb.expect(gdb_prompt) + +def test(command, pattern): + """Sends 'command' to gdb and expects the given 'pattern'. + + If 'pattern' is None, simply consumes everything up to and including + the gdb prompt. + + Args: + command (string): The command we'll send to gdb. + pattern (raw string): A pattern the gdb output should match. + + Returns: + string: The string that matched 'pattern', or an empty string if + 'pattern' was None. + """ + + match = '' + + gdb.sendline(command) + + if pattern: + # PExpect does a non-greedy match for '+' and '*'. Since it can't look + # ahead on the gdb output stream, if 'pattern' ends with a '+' or a '*' + # we may end up matching only part of the required output. + # To avoid this, we'll consume 'pattern' and anything that follows it + # up to and including the gdb prompt, then extract 'pattern' later. + index = gdb.expect([r'{0}.+{1}'.format(pattern, gdb_prompt), + pexpect.TIMEOUT]) + + if index == 0: + # gdb.after now contains the whole match. Extract the text that + # matches 'pattern'. + match = re.match(pattern, gdb.after, re.DOTALL).group() + elif index == 1: + # We got a timeout exception. Print information on what caused it + # and bail out. + error = ('Response does not match the expected pattern.\n' + 'Command: {0}\n' + 'Expected pattern: {1}\n' + 'Response: {2}'.format(command, pattern, gdb.before)) + + raise pexpect.TIMEOUT(error) + else: + # Consume just the the gdb prompt. + gdb.expect(gdb_prompt) + + return match + +def init_test(test_bin): + """Loads the test binary file to gdb. + + Args: + test_bin (string): The name of the test binary file. + """ + + test('file {0}'.format(test_bin), None) + +def go_to_main(): + """Executes a gdb 'start' command, which takes us to main.""" + + test('start', r'main') + +def get_line_number(file_name, string): + """Returns the number of the line in which 'string' appears within a file. + + Args: + file_name (string): The name of the file we'll search through. + string (string): The string we'll look for. + + Returns: + int: The number of the line in which 'string' appears, starting from 1. + """ + number = -1 + + with open(file_name) as src_file: + for i, line in enumerate(src_file): + if string in line: + number = i + 1 + break + + if number == -1: + raise NoLineError(file_name, string) + + return number + +def break_at(file_name, string, temporary=True, thread=None): + """Places a breakpoint on the first line in 'file_name' containing 'string'. + + 'string' is usually a comment like "Stop here". Notice this may fail unless + the comment is placed inline next to actual code, e.g.: + + ... + /* Stop here */ + ... + + may fail, while: + + ... + some_func(); /* Stop here */ + ... + + will succeed. + + If 'thread' isn't None, the breakpoint will be set for all the threads. + Otherwise, it'll be set only for 'thread'. + + Args: + file_name (string): The name of the file we'll place the breakpoint in. + string (string): A string we'll look for inside the file. + We'll place a breakpoint on the line which contains it. + temporary (bool): Whether the breakpoint should be automatically deleted + after we reach it. + thread (int): The number of the thread we'll place the breakpoint for, + as seen by gdb. If specified, it should be greater than zero. + """ + + if not thread: + thread_str = '' + else: + thread_str = 'thread {0}'.format(thread) + + if temporary: + command = 'tbreak' + break_type = 'Temporary breakpoint' + else: + command = 'break' + break_type = 'Breakpoint' + + line_number = str(get_line_number(file_name, string)) + + test('{0} {1}:{2} {3}'.format(command, file_name, line_number, thread_str), + r'{0} [0-9]+ at 0x[a-f0-9]+: file {1}, line {2}\.'.format(break_type, + file_name, + line_number)) + +def continue_cmd(thread=None): + """Executes a gdb 'continue' command. + + If 'thread' isn't None, the command will be applied to all the threads. + Otherwise, it'll be applied only to 'thread'. + + Args: + thread (int): The number of the thread we'll apply the command to, + as seen by gdb. If specified, it should be greater than zero. + """ + + if not thread: + command = 'continue' + else: + command = 'thread apply {0} continue'.format(thread) + + test(command, None) + +def next_cmd(count=1, thread=None): + """Executes a gdb 'next' command. + + If 'thread' isn't None, the command will be applied to all the threads. + Otherwise, it'll be applied only to 'thread'. + + Args: + count (int): The 'count' argument of the 'next' command. + thread (int): The number of the thread we'll apply the command to, + as seen by gdb. If specified, it should be greater than zero. + """ + + if not thread: + command = 'next' + else: + command = 'thread apply {0} next' + + test('{0} {1}'.format(command, count), None) + +def select_thread(thread): + """Selects the thread indicated by 'thread'. + + Args: + thread (int): The number of the thread we'll switch to, as seen by gdb. + This should be greater than zero. + """ + + if thread > 0: + test('thread {0}'.format(thread), None) + +def get_current_thread_lwpid(): + """Gets the current thread's Lightweight Process ID. + + Returns: + string: The current thread's LWP ID. + """ + + # It's easier to get the LWP ID through the Python API than the gdb CLI. + command = 'python print(gdb.selected_thread().ptid[1])' + + return test(command, r'[0-9]+') + +def set_scheduler_locking(mode): + """Executes the gdb 'set scheduler-locking' command. + + Args: + mode (bool): Whether the scheduler locking mode should be 'on'. + """ + modes = { + True: 'on', + False: 'off' + } + + test('set scheduler-locking {0}'.format(modes[mode]), None) + +def test_printer(var, to_string, children=None, is_ptr=True): + """ Tests the output of a pretty printer. + + For a variable called 'var', this tests whether its associated printer + outputs the expected 'to_string' and children (if any). + + Args: + var (string): The name of the variable we'll print. + to_string (raw string): The expected output of the printer's 'to_string' + method. + children (map {raw string->raw string}): A map with the expected output + of the printer's children' method. + is_ptr (bool): Whether 'var' is a pointer, and thus should be + dereferenced. + """ + + if is_ptr: + var = '*{0}'.format(var) + + test('print {0}'.format(var), to_string) + + if children: + for name, value in children.items(): + # Children are shown as 'name = value'. + test('print {0}'.format(var), r'{0} = {1}'.format(name, value)) diff --git a/scripts/gen-py-const.awk b/scripts/gen-py-const.awk new file mode 100644 index 0000000..4586f59 --- /dev/null +++ b/scripts/gen-py-const.awk @@ -0,0 +1,118 @@ +# Script to generate constants for Python pretty printers. +# +# Copyright (C) 2016 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 script is a smaller version of the clever gen-asm-const.awk hack used to +# generate ASM constants from .sym files. We'll use this to generate constants +# for Python pretty printers. +# +# The input to this script are .pysym files that look like: +# #C_Preprocessor_Directive... +# NAME1 +# NAME2 expression... +# +# A line giving just a name implies an expression consisting of just that name. +# Comments start with '--'. +# +# The output of this script is a 'dummy' function containing 'asm' declarations +# for each non-preprocessor line in the .pysym file. The expression values +# will appear as input operands to the 'asm' declaration. For example, if we +# have: +# +# /* header.h */ +# #define MACRO 42 +# +# struct S { +# char c1; +# char c2; +# char c3; +# }; +# +# enum E { +# ZERO, +# ONE +# }; +# +# /* symbols.pysym */ +# #include <stddef.h> +# #include "header.h" +# -- This is a comment +# MACRO +# C3_OFFSET offsetof(struct S, c3) +# E_ONE ONE +# +# the output will be: +# +# #include <stddef.h> +# #include "header.h" +# void dummy(void) +# { +# asm ("@name@MACRO@value@%0@" : : "i" (MACRO)); +# asm ("@name@C3_OFFSET@value@%0@" : : "i" (offsetof(struct S, c3))); +# asm ("@name@E_ONE@value@%0@" : : "i" (ONE)); +# } +# +# We'll later feed this output to gcc -S. Since '-S' tells gcc to compile but +# not assemble, gcc will output something like: +# +# dummy: +# ... +# @name@MACRO@value@$42@ +# @name@C3_OFFSET@value@$2@ +# @name@E_ONE@value@$1@ +# +# Finally, we can process that output to extract the constant values. +# Notice gcc may prepend a special character such as '$' to each value. + +# found_symbol indicates whether we found a non-comment, non-preprocessor line. +BEGIN { found_symbol = 0 } + +# C preprocessor directives go straight through. +/^#/ { print; next; } + +# Skip comments. +/--/ { next; } + +# Trim leading whitespace. +{ sub(/^[[:blank:]]*/, ""); } + +# If we found a non-comment, non-preprocessor line, print the 'dummy' function +# header. +NF > 0 && !found_symbol { + print "void dummy(void)\n{"; + found_symbol = 1; +} + +# If the line contains just a name, duplicate it so we can use that name +# as the value of the expression. +NF == 1 { sub(/^.*$/, "& &"); } + +# If a line contains a name and an expression... +NF > 1 { + name = $1; + + # Remove any characters before the second field. + sub(/^[^[:blank:]]+[[:blank:]]+/, ""); + + # '$0' ends up being everything that appeared after the first field + # separator. + printf " asm (\"@name@%s@value@%0@\" : : \"i\" (%s));\n", name, $0; +} + +# Close the 'dummy' function. +END { if (found_symbol) print "}"; } |