From patchwork Sun Dec 31 19:24:00 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Florian Weimer X-Patchwork-Id: 854262 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (mailfrom) smtp.mailfrom=sourceware.org (client-ip=209.132.180.131; helo=sourceware.org; envelope-from=libc-alpha-return-88687-incoming=patchwork.ozlabs.org@sourceware.org; receiver=) Authentication-Results: ozlabs.org; dkim=pass (1024-bit key; secure) header.d=sourceware.org header.i=@sourceware.org header.b="SLGVR9vo"; dkim-atps=neutral Received: from sourceware.org (server1.sourceware.org [209.132.180.131]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 3z8qyb1qHNz9s84 for ; Mon, 1 Jan 2018 06:24:38 +1100 (AEDT) DomainKey-Signature: a=rsa-sha1; c=nofws; d=sourceware.org; h=list-id :list-unsubscribe:list-subscribe:list-archive:list-post :list-help:sender:date:to:subject:mime-version:content-type :content-transfer-encoding:message-id:from; q=dns; s=default; b= ktYu0QYuGR75mG5l21pPZYVMaIp4zPS/l64ycK28yZ7X8bYdivBW/oPrQTMRVrU1 SMu4gcgY3vMkfGnGcAIb6DPt1o4CktIWRBtw1f2zf+4JGfKW2D1UCnw9a5Vbt63d 3MZKxtNgrBI6JYHNuHKoRYAM3u6NrQwq/Rocwr60If8= DKIM-Signature: v=1; a=rsa-sha1; c=relaxed; d=sourceware.org; h=list-id :list-unsubscribe:list-subscribe:list-archive:list-post :list-help:sender:date:to:subject:mime-version:content-type :content-transfer-encoding:message-id:from; s=default; bh=5T7KcZ 4fWF7B70lzlLE/vtq4GT4=; b=SLGVR9vokizdDX/qA/LBMpXeBLhamu4zWbjXfQ bdSs6aC1IfGLzVsW1Zl+XNCOx2b2daBvO/Z4bCRYkZDPJGVkQHcg1X+YU7RTQQih iRfec0dEQHvd7ARrhBKTuXGAs/T9osBOKxV3icclfaPG/MkFLCgnAHuOWjZRbUku AgScs= Received: (qmail 110640 invoked by alias); 31 Dec 2017 19:24:17 -0000 Mailing-List: contact libc-alpha-help@sourceware.org; run by ezmlm Precedence: bulk List-Id: List-Unsubscribe: List-Subscribe: List-Archive: List-Post: List-Help: , Sender: libc-alpha-owner@sourceware.org Delivered-To: mailing list libc-alpha@sourceware.org Received: (qmail 109621 invoked by uid 89); 31 Dec 2017 19:24:04 -0000 Authentication-Results: sourceware.org; auth=none X-Virus-Found: No X-Spam-SWARE-Status: No, score=-26.9 required=5.0 tests=BAYES_00, GIT_PATCH_0, GIT_PATCH_1, GIT_PATCH_2, GIT_PATCH_3, KAM_SHORT, SPF_HELO_PASS, T_RP_MATCHES_RCVD autolearn=ham version=3.3.2 spammy=2298, accident, thr X-HELO: mx1.redhat.com Date: Sun, 31 Dec 2017 20:24:00 +0100 To: libc-alpha@sourceware.org Subject: [PATCH] nptl: Add test for callee-saved register restore in pthread_exit User-Agent: Heirloom mailx 12.5 7/5/10 MIME-Version: 1.0 Message-Id: <20171231192400.46AB7401BC22B@oldenburg.str.redhat.com> From: fweimer@redhat.com (Florian Weimer) GCC PR 83641 results in a miscompilation of libpthread, which causes pthread_exit not to restore callee-saved registers before running destructors for objects on the stack. This test detects this situation: info: unsigned int, direct pthread_exit call tst-thread-exit-clobber.cc:80: numeric comparison failure left: 4148288912 (0xf741dd90); from: value right: 1600833940 (0x5f6ac994); from: magic_values.v2 info: double, direct pthread_exit call info: unsigned int, indirect pthread_exit call info: double, indirect pthread_exit call error: 1 test failures 2017-12-31 Florian Weimer * nptl/tst-thread-exit-clobber.cc: New file. * nptl/Makefile (CFLAGS-tst-thread-exit-clobber.o): Compile in C++11 mode. (LDLIBS-tst-thread-exit-clobber): Link with libstdc++. (tests): Add tst-thread-exit-clobber. [!CXX] (tests-unsupported): Add tst-thread-exit-clobber. Reviewed-by: Carlos O'Donell diff --git a/nptl/Makefile b/nptl/Makefile index cf2ba8131b..83ecdc6330 100644 --- a/nptl/Makefile +++ b/nptl/Makefile @@ -229,6 +229,8 @@ CFLAGS-pt-system.c += -fexceptions LDLIBS-tst-once5 = -lstdc++ CFLAGS-tst-thread_local1.o = -std=gnu++11 LDLIBS-tst-thread_local1 = -lstdc++ +CFLAGS-tst-thread-exit-clobber.o = -std=gnu++11 +LDLIBS-tst-thread-exit-clobber = -lstdc++ tests = tst-attr1 tst-attr2 tst-attr3 tst-default-attr \ tst-mutex1 tst-mutex2 tst-mutex3 tst-mutex4 tst-mutex5 tst-mutex6 \ @@ -304,7 +306,8 @@ tests = tst-attr1 tst-attr2 tst-attr3 tst-default-attr \ c89 gnu89 c99 gnu99 c11 gnu11) \ tst-bad-schedattr \ tst-thread_local1 tst-mutex-errorcheck tst-robust10 \ - tst-robust-fork tst-create-detached tst-memstream + tst-robust-fork tst-create-detached tst-memstream \ + tst-thread-exit-clobber tests-internal := tst-rwlock19 tst-rwlock20 \ tst-sem11 tst-sem12 tst-sem13 \ @@ -458,7 +461,7 @@ tests-unsupported += tst-cancel24 tst-cancel24-static tst-once5 endif # These tests require a C++ compiler and runtime with thread_local support. ifneq ($(have-cxx-thread_local),yes) -tests-unsupported += tst-thread_local1 +tests-unsupported += tst-thread_local1 tst-thread-exit-clobber endif include ../Rules diff --git a/nptl/tst-thread-exit-clobber.cc b/nptl/tst-thread-exit-clobber.cc new file mode 100644 index 0000000000..95b81f1e80 --- /dev/null +++ b/nptl/tst-thread-exit-clobber.cc @@ -0,0 +1,234 @@ +/* Test that pthread_exit does not clobber callee-saved registers. + Copyright (C) 2017 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 + . */ + +/* This test attempts to check that callee-saved registers are + restored to their original values when destructors are run after + pthread_exit is called. GCC PR 83641 causes this test to fail. */ + +#include +#include +#include + +/* These constants are magic values which are used to detect whether + registers have been clobbered. The idea is that these values are + hidden behind a compiler barrier and only present in .rodata + initially, so that it is less likely that they are in a register by + accident. */ + +template +struct values +{ + T v0; + T v1; + T v2; + T v3; + T v4; +}; + +static const values magic_values = + { + 0x57f7fc72, + 0xe582daba, + 0x5f6ac994, + 0x35efddb7, + 0x1fbf5a74, + }; + +static const values magic_values_double = + { + 0.6764041905675465, + 0.9533336788140494, + 0.6091161359041452, + 0.7668653957125336, + 0.010374520235509666, + }; + +/* Special index value which tells check_magic that no check should be + performed. */ +enum { no_check = -1 }; + +/* Check that VALUE is the magic value for INDEX, behind a compiler + barrier. */ +__attribute__ ((noinline, noclone, weak)) +void +check_magic (int index, unsigned int value) +{ + switch (index) + { + case 0: + TEST_COMPARE (value, magic_values.v0); + break; + case 1: + TEST_COMPARE (value, magic_values.v1); + break; + case 2: + TEST_COMPARE (value, magic_values.v2); + break; + case 3: + TEST_COMPARE (value, magic_values.v3); + break; + case 4: + TEST_COMPARE (value, magic_values.v4); + break; + case no_check: + break; + default: + FAIL_EXIT1 ("invalid magic value index %d", index); + } +} + +/* Check that VALUE is the magic value for INDEX, behind a compiler + barrier. Double variant. */ +__attribute__ ((noinline, noclone, weak)) +void +check_magic (int index, double value) +{ + switch (index) + { + case 0: + TEST_VERIFY (value == magic_values_double.v0); + break; + case 1: + TEST_VERIFY (value == magic_values_double.v1); + break; + case 2: + TEST_VERIFY (value == magic_values_double.v2); + break; + case 3: + TEST_VERIFY (value == magic_values_double.v3); + break; + case 4: + TEST_VERIFY (value == magic_values_double.v4); + break; + case no_check: + break; + default: + FAIL_EXIT1 ("invalid magic value index %d", index); + } +} + +/* Store a magic value and check, via the destructor, that it has the + expected value. */ +template +struct checker +{ + T value; + + checker (T v) + : value (v) + { + } + + ~checker() + { + check_magic (I, value); + } +}; + +/* The functions call_pthread_exit_0, call_pthread_exit_1, + call_pthread_exit are used to call pthread_exit indirectly, with + the intent of clobbering the register values. */ + +__attribute__ ((noinline, noclone, weak)) +void +call_pthread_exit_0 (const values *pvalues) +{ + checker c0 (pvalues->v0); + checker c1 (pvalues->v1); + checker c2 (pvalues->v2); + checker c3 (pvalues->v3); + checker c4 (pvalues->v4); + + pthread_exit (NULL); +} + +__attribute__ ((noinline, noclone, weak)) +void +call_pthread_exit_1 (const values *pvalues) +{ + checker c0 (pvalues->v0); + checker c1 (pvalues->v1); + checker c2 (pvalues->v2); + checker c3 (pvalues->v3); + checker c4 (pvalues->v4); + + values other_values = { 0, }; + call_pthread_exit_0 (&other_values); +} + +__attribute__ ((noinline, noclone, weak)) +void +call_pthread_exit () +{ + values other_values = { 0, }; + call_pthread_exit_1 (&other_values); +} + +/* Create on-stack objects and check that their values are restored by + pthread_exit. If Nested is true, call pthread_exit indirectly via + call_pthread_exit. */ +template +__attribute__ ((noinline, noclone, weak)) +void * +threadfunc (void *closure) +{ + const values *pvalues = static_cast *> (closure); + + checker c0 (pvalues->v0); + checker c1 (pvalues->v1); + checker c2 (pvalues->v2); + checker c3 (pvalues->v3); + checker c4 (pvalues->v4); + + if (Nested) + call_pthread_exit (); + else + pthread_exit (NULL); + + /* This should not be reached. */ + return const_cast (""); +} + +static int +do_test () +{ + puts ("info: unsigned int, direct pthread_exit call"); + pthread_t thr + = xpthread_create (NULL, &threadfunc, + const_cast *> (&magic_values)); + TEST_VERIFY (xpthread_join (thr) == NULL); + + puts ("info: double, direct pthread_exit call"); + thr = xpthread_create (NULL, &threadfunc, + const_cast *> (&magic_values_double)); + TEST_VERIFY (xpthread_join (thr) == NULL); + + puts ("info: unsigned int, indirect pthread_exit call"); + thr = xpthread_create (NULL, &threadfunc, + const_cast *> (&magic_values)); + TEST_VERIFY (xpthread_join (thr) == NULL); + + puts ("info: double, indirect pthread_exit call"); + thr = xpthread_create (NULL, &threadfunc, + const_cast *> (&magic_values_double)); + TEST_VERIFY (xpthread_join (thr) == NULL); + + return 0; +} + +#include