From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 29112C77B7C for ; Mon, 1 May 2023 20:05:21 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S232785AbjEAUFU (ORCPT ); Mon, 1 May 2023 16:05:20 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:50930 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S232740AbjEAUFS (ORCPT ); Mon, 1 May 2023 16:05:18 -0400 Received: from mx0b-00069f02.pphosted.com (mx0b-00069f02.pphosted.com [205.220.177.32]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 55D051707 for ; Mon, 1 May 2023 13:05:14 -0700 (PDT) Received: from pps.filterd (m0246630.ppops.net [127.0.0.1]) by mx0b-00069f02.pphosted.com (8.17.1.19/8.17.1.19) with ESMTP id 341JmxiI027635; Mon, 1 May 2023 20:04:52 GMT DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=oracle.com; h=from : to : cc : subject : date : message-id : in-reply-to : references : content-transfer-encoding : content-type : mime-version; s=corp-2023-03-30; bh=StgNAcU0SWfdw9cHGAwZw9E1wxZMsCHza0FxWAlzLH0=; b=gVqTDHTGU9U7Z0rjsVid5e+MoBLVQsy3SQ6JwnY0kzC691wF6epv6u75fm7oRFlZiRmP ecem+3C0Cz8ZQ5pk0jYHxi5rGu7T3s34TZ9ADsL/yoLk6aG27yRHJS+QEabRgmihHDI5 KIvmmhjJr4g6G1Izg8c0+LvDXj/LwgbIF6QTxRCHOKI7uhv2YQ57bcnnNDI19hw3Hzwp fp4S54gDHXXjQf+2Kh5alAA16uVkMLDqYjhukn0QgreftNdTNsW002eKAU++Vq0w9Wds qlsH2bRUbHqauFQ7/uNZUPhZLzhK30kz+qy7qjS+TgK5O9MgmI2i81tTNYgUScG/SUTP YQ== Received: from phxpaimrmta01.imrmtpd1.prodappphxaev1.oraclevcn.com (phxpaimrmta01.appoci.oracle.com [138.1.114.2]) by mx0b-00069f02.pphosted.com (PPS) with ESMTPS id 3q8sne35gh-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=OK); Mon, 01 May 2023 20:04:52 +0000 Received: from pps.filterd (phxpaimrmta01.imrmtpd1.prodappphxaev1.oraclevcn.com [127.0.0.1]) by phxpaimrmta01.imrmtpd1.prodappphxaev1.oraclevcn.com (8.17.1.19/8.17.1.19) with ESMTP id 341J3isY036187; Mon, 1 May 2023 20:04:51 GMT Received: from nam12-bn8-obe.outbound.protection.outlook.com (mail-bn8nam12lp2175.outbound.protection.outlook.com [104.47.55.175]) by phxpaimrmta01.imrmtpd1.prodappphxaev1.oraclevcn.com (PPS) with ESMTPS id 3q8sp4uwap-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=OK); Mon, 01 May 2023 20:04:51 +0000 ARC-Seal: i=1; a=rsa-sha256; s=arcselector9901; d=microsoft.com; cv=none; b=PGTS/twEdYKbNLhPc3G0Uvz/Wqs5nj4ypNQTpTTx0EcDTYOJ5eBvHJrplydDQmSrx+r55aoP65Z7gOh0Hb2YWPYjb4pfwdW4VClAjuFn94yvCyMZbPdmqzUlarW34KJzvijdoTa1PzLGvka2EPNB8SBBPdtj2wFRG/AF5+5iA2JiyjjvCmqHBDxYiQuvCsoHaxDA7rJjzTqw0qTMeK6hBNtLoK1iyfI03enC3egf1K0xPc48hZ8tcpt22Z3+8t+SQi+HmTW8DECzpayKS2C8py5TTBkhmQvATtbJ2dyTxwkr5CNLeCeVpsHZl3nSRr2FQ0y2IGFnxLw1m+L6w9Ek8g== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com; s=arcselector9901; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-AntiSpam-MessageData-ChunkCount:X-MS-Exchange-AntiSpam-MessageData-0:X-MS-Exchange-AntiSpam-MessageData-1; bh=StgNAcU0SWfdw9cHGAwZw9E1wxZMsCHza0FxWAlzLH0=; b=Na7+T1zdeaZe83z+3Wi4QSw1MymlugR0n4Sp/oCaJmhkjwY87cE99VRbTXogr6buJblrZVWW8A5sAR/2/G18CMAb6HU820VS7/h7+MZebAwY551mvrg1HvA2Dmm72Qk9u8z0+DqMkZwqLHSlUzGlrz+h4Ko+Fku158a5m2q/gesugWSDtGm/yP3RdWd8RqN5qYaBmzsucwjVtGcbFEd52JNx3qF+q1lgyM+tA+e6AlLkbFkRu7BdA5NN3xqdyTBku7VelElEdbKCmh1c1HhwULhKIopYLPmae3vAoTQnqt7KSUTOGMKfAzw9Nvqtv2bqhiN/pWE0vKp4MhlJuIOz5A== ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=pass smtp.mailfrom=oracle.com; dmarc=pass action=none header.from=oracle.com; dkim=pass header.d=oracle.com; arc=none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=oracle.onmicrosoft.com; s=selector2-oracle-onmicrosoft-com; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck; bh=StgNAcU0SWfdw9cHGAwZw9E1wxZMsCHza0FxWAlzLH0=; b=YxBW9IN25rVx3arKfEeowwwCIYkAEao1FfopN9w/ya/l+7SunlFTIe/mWeBRFHW8HjIQoZ60kNVwaTIF/mq/G4UKhAiwskSG0ifkMWfErrfG6ds7V8SC4hWtO113nKs8jQaL/ugY4K24kz30jEVQTUtu9/hNl2uT6sAoHt8tnTE= Received: from MWHPR1001MB2158.namprd10.prod.outlook.com (2603:10b6:301:2d::17) by SA2PR10MB4505.namprd10.prod.outlook.com (2603:10b6:806:112::9) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.6340.30; Mon, 1 May 2023 20:04:48 +0000 Received: from MWHPR1001MB2158.namprd10.prod.outlook.com ([fe80::ff1c:f1fb:8db9:22e2]) by MWHPR1001MB2158.namprd10.prod.outlook.com ([fe80::ff1c:f1fb:8db9:22e2%6]) with mapi id 15.20.6340.030; Mon, 1 May 2023 20:04:47 +0000 From: Indu Bhagat To: linux-toolchains@vger.kernel.org Cc: daandemeyer@meta.com, andrii@kernel.org, rostedt@goodmis.org, kris.van.hees@oracle.com, elena.zannoni@oracle.com, nick.alcock@oracle.com, Indu Bhagat Subject: [POC 4/5] sframe: add an SFrame format stack tracer Date: Mon, 1 May 2023 13:04:09 -0700 Message-Id: <20230501200410.3973453-5-indu.bhagat@oracle.com> X-Mailer: git-send-email 2.39.2 In-Reply-To: <20230501200410.3973453-1-indu.bhagat@oracle.com> References: <20230501200410.3973453-1-indu.bhagat@oracle.com> Content-Transfer-Encoding: 8bit Content-Type: text/plain X-ClientProxiedBy: MW4PR02CA0007.namprd02.prod.outlook.com (2603:10b6:303:16d::33) To MWHPR1001MB2158.namprd10.prod.outlook.com (2603:10b6:301:2d::17) MIME-Version: 1.0 X-MS-PublicTrafficType: Email X-MS-TrafficTypeDiagnostic: MWHPR1001MB2158:EE_|SA2PR10MB4505:EE_ X-MS-Office365-Filtering-Correlation-Id: 323070e2-43c3-40a7-227b-08db4a7f5075 X-MS-Exchange-SenderADCheck: 1 X-MS-Exchange-AntiSpam-Relay: 0 X-Microsoft-Antispam: BCL:0; X-Microsoft-Antispam-Message-Info: 6Qy/OCl2X76jFLGzfceigSaT3OsibIQQh3vYpsMWNILm++mKdFCrb55Gd2GfXqckB0OlYkQYQvgm8LnARZDbc/1PE1IkZw2NeunyR8U1aj1rOS/o+oiMI8YdA99SzGCurf6Af4CN3jklMl5zBF/8INLwUWwSsIaU5XE68n6YJkK1LnkS252xnSms2Mb5fyr+fpRuBYahcNzrOaPTzeKQ5naDAhvvx0r+KtYdGkHE8l6MTDxJUEhHALpvNw1R0gNyN2305qVgAMF/GEoB5WijWKFm4VABUIvDfyS8EtXfYUSseMkFHrI3+nKq2UK6MAQvcmje3S1ZFyG8UiXNaPbmPO9N3S2Fc8QDb2Dk0h8SBKNxZJ5LqWJf2hASQZ253Ju0lsTJTB4JAH9C5aHWzMp9u6Yz+r12SjctKQvhjWGiDBP8KDVtuYllJ/j9hBZZvIlZsZ6LlBmm0pt/qdwaLcMD3dn85p5lWjnTjBNPuqDKcxV7hj6TLHSL+nhFQZqOtDYk692ymbAE791ZTKC0sZKVR/gPcWAbXEbfQVlEoPjZkJJcxdDzAZHcbtI8c85e11uI X-Forefront-Antispam-Report: CIP:255.255.255.255;CTRY:;LANG:en;SCL:1;SRV:;IPV:NLI;SFV:NSPM;H:MWHPR1001MB2158.namprd10.prod.outlook.com;PTR:;CAT:NONE;SFS:(13230028)(346002)(376002)(136003)(396003)(39860400002)(366004)(451199021)(107886003)(6666004)(36756003)(6486002)(30864003)(2906002)(66899021)(6506007)(6512007)(1076003)(66946007)(66476007)(6916009)(4326008)(66556008)(38100700002)(186003)(478600001)(316002)(2616005)(8676002)(8936002)(41300700001)(44832011)(5660300002)(83380400001)(86362001);DIR:OUT;SFP:1101; X-MS-Exchange-AntiSpam-MessageData-ChunkCount: 1 X-MS-Exchange-AntiSpam-MessageData-0: =?us-ascii?Q?Pu6CJ5oXy2xF3mKKL/RDzga/lw52orqVpr/tiRNiQvN7s5215GDLS4MQCLX7?= =?us-ascii?Q?zU8UULSrFzNyZ0pUureBIYw1j2UvA2OW9X1ak4SekPiXK8HAaVp1e+Cyrmvj?= =?us-ascii?Q?WUIx+EQkRPvIT6TVit5SSmPXFRLacrDYKgTETiaoDG9NyXkbOUZX3hyGxfbP?= =?us-ascii?Q?7aclEgSbMLCodMkn1310Sl1BNcUY2bMWUXgza0YMsTjpjtNm9Lj3q3aQShaA?= =?us-ascii?Q?YBYLL+PsyAF7TXC+ZuT8xYCOfTaS/VzuDDDmZHljuEriTqSw3hVxjTjVYnLx?= =?us-ascii?Q?y1VYfQz3A7rmGOxZSgqOb6krkC1I5xQpFxqqcJcdzOB7z4a9x+1lKM3Kc5vf?= =?us-ascii?Q?odYNc4nL5nvGfIw7gqbOzbaAu5joyOrPlTkdsPnUlAmg2oDYCC/277h1m60h?= =?us-ascii?Q?/vFig1XqmYjtNXRr+AFCoBnVkD7L+N3dNk48EkFov1iQLfG6X94WpbJ3T08D?= =?us-ascii?Q?hg6CGCeDqCJff183gDx+ofn6kILVUhKW3lvMFeu64F19SdUZOkqTLFVHihmn?= =?us-ascii?Q?Q6Wym+ehc9X7WquwCv8OpfcgaFFcUEw1c69ggiTzmsRJkKICP0Rl+ROHWN5G?= =?us-ascii?Q?k89CyfULdj16MiL+0Jg9EtF4aX7CuJ42Ws/i4R3Vdgj8UDAtrlRUiWYBdkAx?= =?us-ascii?Q?W6GkXTLC8fKjWVLXs6ld7Z1X81n5cx+KXM+mX1m+XlaQze8PjftTiXjlhUG6?= =?us-ascii?Q?koDaZjVcODxnWJqThUsb7pjQuY8gejqnFewxRp/Shz7pBdV6hZLl7W3zPUAf?= =?us-ascii?Q?xC5c2YDRQcxOui671jbZERLF/aEzTr2olJ8wUyFJS7hflv1nd6jNUdupuk9O?= =?us-ascii?Q?7SrMHkgC3kpaZYIZpxOgqHM/fRe61XoQcJsFvA5X2snrpB46cocxaTihYwOs?= =?us-ascii?Q?r3WOQ6nt/odGUAt9jfRwNyyzPV/eYcbBRd+jyq+0z0+nYqWTaHP0sZ30BYoC?= =?us-ascii?Q?5XX64pv9qzQgNb3Lm7m/oOEN9Oweew8XEZJ1Pujimy3qeT/2McL/Hxp6aFyT?= =?us-ascii?Q?kvgrriFYZwzL7YIGBRl+Towet5Zuc866KYY45cOYKCF2kBHWwJCAKcJmgwm6?= =?us-ascii?Q?KgowLHoeP94HmHI8Y3oqof1jSlEtVBFRz5GVpURrsj+EqVD6cYOmHNsc6Wwn?= =?us-ascii?Q?4B4PTeTgaRKTvzzqw6dwFbf5i8q39An/cjxdbH/z2Jx4gTKdP5DWeFUw+59j?= =?us-ascii?Q?BlV1abG5rGx+myBqBsc9gFpqquPD959XcodME6zdYzUxr9Jox/P8pCeUHAz7?= =?us-ascii?Q?u1jf4Um4m69i2HFQfvFUDyz1RCwsJBkM3X7gBEHJWGlLnk2dJy02wtLl5efn?= =?us-ascii?Q?iYHxHzWzmrlFzNU+7bTpWgvxNKSl0tBmGlieh7KMspk4iiOELWagg/6d5J4X?= =?us-ascii?Q?ECdt/8lSuerigohc0+/JtatAd7jAhA3ySVR5L0Rc6T4B2iDxa8ysyCUz08mb?= =?us-ascii?Q?+xMUqNtd26xwxh5lLunyKFFfR0IZYHpuv8koLGWPWjQrVPzISwZy/7sCCWGJ?= =?us-ascii?Q?zXV9socA2exngGVfNFH0rrUNsi9BfK/8BT1KUE0dCLtas/svR73M+7DLawHp?= =?us-ascii?Q?8oVEPbDRmAV06tBqQH/ye8ULtY34Gx20BEtZbGberZM/0LHc4lP+3dxAMa4a?= =?us-ascii?Q?GJg0Orf4Li2PuIj6PTNAqIk=3D?= X-MS-Exchange-AntiSpam-ExternalHop-MessageData-ChunkCount: 1 X-MS-Exchange-AntiSpam-ExternalHop-MessageData-0: qJYN40R2QIWnVXOIZQPdnjK6xlLlf37VrQbPQiHqqj91YMSRrGeWwzjBNSGFNcZf+vJojJV8KgSVNYu5pPjbCeUffxr/Blzp4LEC5+vGwcBtOw+z0Vk1hZyl3/1kudy15WZXmOPymiCkem5YZgZ0AUiMx5cVijaK0/D4PpGneXmNHjpzR2uqwyDOixRlWrY4pBJU4AzlNPhrpzEJ1WsI7gMsRv785+riMDhrgXUyu+wwf+W6DBP8WUbqhV3ha7j902DTR46sDmxNRnOeILajwf2LiSeCHLkqEgbuXFyuugK59B/EMvkj+s31HOTMpeAgtaMSaBh1qqtRdcgirPInAVicSuWXXpidgn5znQJ8aIvIkkcCrs2fZ53f1qY4j+HHq+w2JmHRtum1/wEyiLV+3qsk29U5gWg+CQ7UfTZJ8vaHFdBBUz8jUX2Hx/ISb1s6BrpltdKWMuzQzTx8QPbnqh9w35lSVGSqwyN27S8kowsaTKFvJtmAoSB9A/ktm7unKEWtoNKiJ+RSJGF2Wb5YXEpnA0640Zmjb8Qp6q788XevWQRpB7Sk328FXJ4LqLZSRGj6nDUAXeeJ1uJcED5fL3E7+YaSrU4MphxScZX6aVCtIxLczqY16NMggYEXK7TfHY6ED+EnlI/Z7c6n2bUoP+FO3UbURK7mUu3PCqzA+4524movMmyEn3UAffSomG3BAgBJpg5rbhqKFgeIbB7kO6y5hwvxOvSCa8GbsWuTM14vZG4tXCr+f8Pxrt0tNBP9KxAHoHE/sfRp+8QXkdnneF5qNsmTDDSCtwfbjIkd4/b1JNXcz3iPV6TKafs6qppbRe9QAX+d3s6Pkw0kwG3jVw== X-OriginatorOrg: oracle.com X-MS-Exchange-CrossTenant-Network-Message-Id: 323070e2-43c3-40a7-227b-08db4a7f5075 X-MS-Exchange-CrossTenant-AuthSource: MWHPR1001MB2158.namprd10.prod.outlook.com X-MS-Exchange-CrossTenant-AuthAs: Internal X-MS-Exchange-CrossTenant-OriginalArrivalTime: 01 May 2023 20:04:47.8596 (UTC) X-MS-Exchange-CrossTenant-FromEntityHeader: Hosted X-MS-Exchange-CrossTenant-Id: 4e2c6054-71cb-48f1-bd6c-3a9705aca71b X-MS-Exchange-CrossTenant-MailboxType: HOSTED X-MS-Exchange-CrossTenant-UserPrincipalName: 4Tkx6QGuFZhcRSr6pfKiyoykacvVpT1p/uEenJR7zOxJpriw2Pm4fpymjaJcuTjfuW6c55JjjNnbZczilM7h2g== X-MS-Exchange-Transport-CrossTenantHeadersStamped: SA2PR10MB4505 X-Proofpoint-Virus-Version: vendor=baseguard engine=ICAP:2.0.254,Aquarius:18.0.942,Hydra:6.0.573,FMLib:17.11.170.22 definitions=2023-05-01_12,2023-04-27_01,2023-02-09_01 X-Proofpoint-Spam-Details: rule=notspam policy=default score=0 bulkscore=0 mlxscore=0 spamscore=0 mlxlogscore=999 phishscore=0 malwarescore=0 adultscore=0 suspectscore=0 classifier=spam adjust=0 reason=mlx scancount=1 engine=8.12.0-2303200000 definitions=main-2305010161 X-Proofpoint-GUID: xRKSnxV_ZgID-6qXfiNM8mq_z81pFLr9 X-Proofpoint-ORIG-GUID: xRKSnxV_ZgID-6qXfiNM8mq_z81pFLr9 Precedence: bulk List-ID: X-Mailing-List: linux-toolchains@vger.kernel.org This patch adds an SFrame format based stack tracer. The files iterate_phdr.c, iterate_phdr.h implement a dl_iterate_phdr() like functionality. The SFrame format based stack tracer is implemented in the sframe_unwind.c with architecture specific bits in the arch/arm64/include/asm/sframe_regs.h and arch/x86/include/asm/sframe_regs.h. Please note that the SFrame format is supported for x86_64 (AMD64 ABI) and aarch64 (AAPCS64 ABI) only at this time. The files sframe_state.[ch] implement the SFrame state management APIs. Some aspects of the implementation are "POC like". These will need to addressed for the implementation to become more palatable: - dealing with only Elf64_Phdr (no Elf32_Phdr) at this time, and other TODOs in the iterate_phdr.c, - detecting whether a program did a dlopen/dlclose, - code stubs around user space memory access (.sframe section, ELF hdr etc.) by the kernel need careful review. There are more aspects than above; The intention of this patch set is to help drive the discussion on how to best incorporate an SFrame-based user space unwinder in the kernel. Signed-off-by: Indu Bhagat --- arch/arm64/include/asm/sframe_regs.h | 37 +++ arch/x86/include/asm/sframe_regs.h | 34 +++ include/sframe/sframe_regs.h | 11 + include/sframe/sframe_unwind.h | 62 ++++ lib/sframe/Makefile | 8 +- lib/sframe/iterate_phdr.c | 113 +++++++ lib/sframe/iterate_phdr.h | 34 +++ lib/sframe/sframe_state.c | 424 +++++++++++++++++++++++++++ lib/sframe/sframe_state.h | 80 +++++ lib/sframe/sframe_unwind.c | 208 +++++++++++++ 10 files changed, 1010 insertions(+), 1 deletion(-) create mode 100644 arch/arm64/include/asm/sframe_regs.h create mode 100644 arch/x86/include/asm/sframe_regs.h create mode 100644 include/sframe/sframe_regs.h create mode 100644 include/sframe/sframe_unwind.h create mode 100644 lib/sframe/iterate_phdr.c create mode 100644 lib/sframe/iterate_phdr.h create mode 100644 lib/sframe/sframe_state.c create mode 100644 lib/sframe/sframe_state.h create mode 100644 lib/sframe/sframe_unwind.c diff --git a/arch/arm64/include/asm/sframe_regs.h b/arch/arm64/include/asm/sframe_regs.h new file mode 100644 index 000000000000..ae9ab9d5d3c1 --- /dev/null +++ b/arch/arm64/include/asm/sframe_regs.h @@ -0,0 +1,37 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2023, Oracle and/or its affiliates. + */ + +#ifdef ASM_ARM64_SFRAME_REGS_H +#define ASM_ARM64_SFRAME_REGS_H + +#define STACK_ACCESS_LEN 8 + +static inline uint64_t +get_ptregs_ip(struct pt_regs *regs) +{ + return regs->pc; +} + +static inline uint64_t +get_ptregs_sp(struct pt_regs *regs) +{ + return regs->sp; +} + +static inline uint64_t +get_ptregs_fp(struct pt_regs *regs) +{ +#define UNWIND_AARCH64_X29 29 /* 64-bit frame pointer. */ + return (uint64_t)regs->regs[UNWIND_AARCH64_X29]; +} + +static inline uint64_t +get_ptregs_ra(struct pt_regs *regs) +{ +#define UNWIND_AARCH64_X30 30 /* 64-bit link pointer. */ + return (uint64_t)regs->regs[UNWIND_AARCH64_X30]; +} + +#endif /* ASM_ARM64_SFRAME_REGS_H */ diff --git a/arch/x86/include/asm/sframe_regs.h b/arch/x86/include/asm/sframe_regs.h new file mode 100644 index 000000000000..99f84955854a --- /dev/null +++ b/arch/x86/include/asm/sframe_regs.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2023, Oracle and/or its affiliates. + */ + +#ifndef ASM_X86_SFRAME_REGS_H +#define ASM_X86_SFRAME_REGS_H + +#define STACK_ACCESS_LEN 8 + +static inline uint64_t +get_ptregs_ip(struct pt_regs *regs) +{ + return (uint64_t)regs->ip; +} + +static inline uint64_t +get_ptregs_sp(struct pt_regs *regs) +{ + return (uint64_t)regs->sp; +} + +static inline uint64_t +get_ptregs_fp(struct pt_regs *regs) +{ + return (uint64_t)regs->bp; +} + +static inline uint64_t +get_ptregs_ra(struct pt_regs *regs) +{ + return 0; /* SFRAME_CFA_FIXED_RA_INVALID */ +} +#endif /* ASM_X86_SFRAME_REGS_H */ diff --git a/include/sframe/sframe_regs.h b/include/sframe/sframe_regs.h new file mode 100644 index 000000000000..32b67f7a7c78 --- /dev/null +++ b/include/sframe/sframe_regs.h @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2023, Oracle and/or its affiliates. + */ + +#ifndef _SFRAME_REGS_H +#define _SFRAME_REGS_H + +#include + +#endif /* _SFRAME_REGS_H */ diff --git a/include/sframe/sframe_unwind.h b/include/sframe/sframe_unwind.h new file mode 100644 index 000000000000..3e2c12816b60 --- /dev/null +++ b/include/sframe/sframe_unwind.h @@ -0,0 +1,62 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2023, Oracle and/or its affiliates. + */ + +#ifndef _SFRAME_UNWIND_H +#define _SFRAME_UNWIND_H + +#include +#include + +#define PT_GNU_SFRAME 0x6474e554 + +/* + * State used for SFrame-based stack tracing for a user space task. + */ +struct user_unwind_state { + uint64_t pc, sp, fp, ra; + enum stack_type stype; + struct task_struct *task; + bool error; +}; + +/* + * APIs for an SFrame based stack tracer. + */ + +void sframe_unwind_start(struct user_unwind_state *state, + struct task_struct *task, struct pt_regs *regs); +bool sframe_unwind_next_frame(struct user_unwind_state *state); +uint64_t sframe_unwind_get_return_address(struct user_unwind_state *state); + +static inline bool sframe_unwind_done(struct user_unwind_state *state) +{ + return state->stype == STACK_TYPE_UNKNOWN; +} + +static inline bool sframe_unwind_error(struct user_unwind_state *state) +{ + return state->error; +} + +/* + * APIs to manage the SFrame state per task for stack tracing. + */ + +extern struct sframe_state *unwind_sframe_state_alloc(struct task_struct *task); +extern int unwind_sframe_state_update(struct task_struct *task); +extern void unwind_sframe_state_cleanup(struct task_struct *task); + +extern bool unwind_sframe_state_valid_p(struct sframe_state *sfstate); +extern bool unwind_sframe_state_ready_p(struct sframe_state *sftate); + +/* + * Get the callchain using SFrame unwind info for the given task. + */ +extern int +sframe_callchain_user(struct task_struct *task, + struct perf_callchain_entry_ctx *entry, + struct pt_regs *regs); + +#endif /* _SFRAME_UNWIND_H */ diff --git a/lib/sframe/Makefile b/lib/sframe/Makefile index 4e4291d9294f..5ee9e3e7ec93 100644 --- a/lib/sframe/Makefile +++ b/lib/sframe/Makefile @@ -1,5 +1,11 @@ # SPDX-License-Identifier: GPL-2.0 ################################## -obj-$(CONFIG_USER_UNWINDER_SFRAME) += sframe_read.o \ +obj-$(CONFIG_USER_UNWINDER_SFRAME) += iterate_phdr.o \ + sframe_read.o \ + sframe_state.o \ + sframe_unwind.o +CFLAGS_iterate_phdr.o += -I $(srctree)/lib/sframe/ -Wno-error=declaration-after-statement CFLAGS_sframe_read.o += -I $(srctree)/lib/sframe/ +CFLAGS_sframe_state.o += -I $(srctree)/lib/sframe/ +CFLAGS_sframe_unwind.o += -I $(srctree)/lib/sframe/ diff --git a/lib/sframe/iterate_phdr.c b/lib/sframe/iterate_phdr.c new file mode 100644 index 000000000000..c10d590ecc67 --- /dev/null +++ b/lib/sframe/iterate_phdr.c @@ -0,0 +1,113 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2023, Oracle and/or its affiliates. + */ + +#include +#include +#include +#include + +#include "iterate_phdr.h" + +/* + * Iterate over the task's memory mappings and find the ELF headers. + * + * This is expected to be called from perf_callchain_user(), so user process + * context is expected. + */ + +int iterate_phdr(int (*callback)(struct phdr_info *info, + struct task_struct *task, + void *data), + struct task_struct *task, void *data) +{ + struct mm_struct *mm; + struct vm_area_struct *vma_mt; + struct page *page; + + Elf64_Ehdr *ehdr; + struct phdr_info phinfo; + + int ret = 0, res = 0; + int err = 0; + bool first = true; + + memset(&phinfo, 0, sizeof(struct phdr_info)); + + mm = task->mm; + + MA_STATE(mas, &mm->mm_mt, 0, 0); + + mas_for_each(&mas, vma_mt, ULONG_MAX) { + /* ELF header has a fixed place in the file, starting at offset + * zero. + */ + if (vma_mt->vm_pgoff) + continue; + + /* For the callback to infer if its the prog or DSO we are + * dealing with. + */ + phinfo.pi_prog = first; + first = false; + /* FIXME TODO + * - This code assumes 64-bit ELF by using Elf64_Ehdr. + * - Detect the case when ELF program headers to be of + * size > 1 page. + */ + + /* FIXME TODO KERNEL + * - get_user_pages_WHAT, which API. + * What flags ? Is this correct ? + */ + ret = get_user_pages_remote(mm, vma_mt->vm_start, 1, FOLL_GET, + &page, &vma_mt, NULL); + if (ret <= 0) + continue; + + /* The first page must have the ELF header. */ + ehdr = vmap(&page, 1, VM_MAP, PAGE_KERNEL); + if (!ehdr) + goto put_page; + + /* Check for magic bytes to make sure this is ehdr. */ + err = 0; + err |= ((ehdr->e_ident[EI_MAG0] != ELFMAG0) + || (ehdr->e_ident[EI_MAG1] != ELFMAG1) + || (ehdr->e_ident[EI_MAG2] != ELFMAG2) + || (ehdr->e_ident[EI_MAG3] != ELFMAG3)); + if (err) + goto unmap; + + /* + * FIXME TODO handle the case when number of program headers is + * greater than or equal to PN_XNUM later. + */ + if (ehdr->e_phnum == PN_XNUM) + goto unmap; + /* + * FIXME TODO handle the case when Elf phdrs span more than one + * page later ? + */ + if ((sizeof(Elf64_Ehdr) + ehdr->e_phentsize * ehdr->e_phnum) + > PAGE_SIZE) + goto unmap; + + /* Save the location of program headers and the phnum. */ + phinfo.pi_addr = vma_mt->vm_start; + phinfo.pi_phdr = (void *)ehdr + ehdr->e_phoff; + phinfo.pi_phnum = ehdr->e_phnum; + + res = callback(&phinfo, task, data); +unmap: + vunmap(ehdr); +put_page: + put_page(page); + + if (res < 0) + break; + } + + return res; +} diff --git a/lib/sframe/iterate_phdr.h b/lib/sframe/iterate_phdr.h new file mode 100644 index 000000000000..78e73cade579 --- /dev/null +++ b/lib/sframe/iterate_phdr.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2023, Oracle and/or its affiliates. + */ + +#ifndef ITERATE_PHDR_H_ +#define ITERATE_PHDR_H_ + +#include + +struct phdr_info { + /* Determine whether prog or DSO. */ + bool pi_prog; + /* Base address. */ + unsigned long pi_addr; + /* Reference to the ELF program headers of the object. */ + void *pi_phdr; + /* Number of entries in the program header table. */ + unsigned int pi_phnum; + /* + * Following two fields are for optimization - keep track of any + * dlopen/dlclose activity done after program startup. + * FIXME TODO Currently unused. + */ + uint64_t pi_adds; + uint64_t pi_subs; +}; + +int iterate_phdr(int (*callback)(struct phdr_info *info, + struct task_struct *task, + void *data), + struct task_struct *task, void *data); + +#endif /* ITERATE_PHDR_H_ */ diff --git a/lib/sframe/sframe_state.c b/lib/sframe/sframe_state.c new file mode 100644 index 000000000000..a34f762acf42 --- /dev/null +++ b/lib/sframe/sframe_state.c @@ -0,0 +1,424 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2023, Oracle and/or its affiliates. + */ + +#include +#include +#include + +#include "sframe_state.h" +#include "iterate_phdr.h" + +#define NUM_OF_DSOS 32 + +static int num_entries = NUM_OF_DSOS; + +/* + * Error codes for SFrame state. + * + * All condition codes less than SFRAME_UNW_INFO_OK are used to indicate + * an unhealthy SFrame state. + */ +enum { + SFRAME_UNW_INVAL_SFRAME = -3, /* An SFrame section is invalid. */ + SFRAME_UNW_NO_SFSTATE = -2, /* SFrame state could not be set up. */ + SFRAME_UNW_NO_PROG_SFRAME = -1, /* No SFrame section in prog. */ + SFRAME_UNW_INFO_OK = 0, + SFRAME_UNW_PARTIAL_INFO = 1, +}; + +static int sframe_unw_info_cleanup(struct sframe_unw_info *sfu_info) +{ + int i; + + if (!sfu_info) + return 1; + + if (sfu_info->sframe_vmap) { + vunmap(sfu_info->sframe_vmap); + sfu_info->sframe_vmap = NULL; + } + + if (sfu_info->sframe_pages) { + for (i = 0; i < sfu_info->sframe_npages; i++) + put_page(sfu_info->sframe_pages[i]); + kfree(sfu_info->sframe_pages); + sfu_info->sframe_pages = NULL; + } + + kfree(sfu_info->sfsec); + sfu_info->sfsec = NULL; + + return 0; +} + +static void sframe_unw_info_init(struct sframe_unw_info *sfu_info, + uint64_t sframe_addr, size_t sframe_size, + uint64_t text_addr, size_t text_size) +{ + if (!sfu_info) + return; + sfu_info->sframe_addr = sframe_addr; + sfu_info->sframe_size = sframe_size; + sfu_info->text_addr = text_addr; + sfu_info->text_size = text_size; + sfu_info->sframe_pages = NULL; + sfu_info->sframe_vmap = NULL; +} + +/* + * Get the user pages containing the SFrame section and set up the SFrame + * section object for the stacktracer to use later. + */ +static int sframe_unw_info_init_sfsec(struct sframe_state *sfstate, + struct sframe_unw_info *sfu_info) +{ + int i; + int err = 0; + struct task_struct *task; + struct vm_area_struct *vma; + struct sframe_sec *sfsec; + struct page **pages; + unsigned long npages; + char *sframe_vmap, *sframe_buf; + + sfsec = kmalloc(sframe_sec_sizeof(), GFP_KERNEL); + if (!sfsec) + return -ENOMEM; + sfu_info->sfsec = sfsec; + + task = sfstate->task; + + vma = find_vma(task->mm, sfu_info->sframe_addr); + npages = vma_pages(vma); + pages = kmalloc((sizeof(struct page *) * npages), GFP_KERNEL); + if (!pages) { + err = -ENOMEM; + goto free_sfsec; + } + +#if 0 + npages = get_user_pages_remote(task->mm, sfu_info->sframe_addr, npages, + FOLL_GET, pages, &vma, NULL); +#endif + npages = get_user_pages_unlocked(vma->vm_start, npages, pages, + FOLL_GET); + if (npages <= 0) + goto free_page; + + sfu_info->sframe_pages = pages; + sfu_info->sframe_npages = npages; + + sframe_vmap = vmap(pages, npages, VM_MAP, PAGE_KERNEL); + if (!sframe_vmap) + goto put_page; + sfu_info->sframe_vmap = sframe_vmap; + + sframe_buf = sframe_vmap + (sfu_info->sframe_addr - vma->vm_start); + err = sframe_sec_init(sfu_info->sfsec, + sframe_buf, + sfu_info->sframe_size); + + /* + * put_page, vunmap should not be done yet as SFrame section will be + * used when do_sframe_unwind (). + * In the rare possibility that this is a corrupt SFrame section, + * clean up the sframe_unw_info object, and signal error so the + * caller. + */ + if (!err) + return 0; + + vunmap(sframe_vmap); +put_page: + for (i = 0; i < npages; i++) + put_page(pages[i]); +free_page: + kfree(pages); +free_sfsec: + kfree(sfu_info->sfsec); + sfu_info->sfsec = NULL; + + return err; +} + +static int sframe_state_unw_info_list_add(struct sframe_state *sfstate, + struct sframe_unw_info *sfu_info) +{ + size_t realloc_size = 0; + + if (sfstate->su_dsos.alloced == 0) { + sfstate->su_dsos.entry + = kcalloc(num_entries, sizeof(struct sframe_unw_info), + GFP_KERNEL); + if (!sfstate->su_dsos.entry) + return -ENOMEM; + sfstate->su_dsos.alloced = num_entries; + } else if (sfstate->su_dsos.used == sfstate->su_dsos.alloced) { + realloc_size = ((sfstate->su_dsos.alloced + num_entries) + * sizeof(struct sframe_unw_info)); + sfstate->su_dsos.entry + = krealloc(sfstate->su_dsos.entry, realloc_size, GFP_KERNEL); + if (!sfstate->su_dsos.entry) + return -ENOMEM; + + memset(&sfstate->su_dsos.entry[sfstate->su_dsos.alloced], 0, + num_entries * sizeof(struct sframe_unw_info)); + sfstate->su_dsos.alloced += num_entries; + } + + sfstate->su_dsos.entry[sfstate->su_dsos.used++] = *sfu_info; + return 0; +} + +static int sframe_state_add_unw_info(struct sframe_state *sfstate, + uint64_t sframe_addr, size_t sframe_size, + uint64_t text_addr, size_t text_size) +{ + struct sframe_unw_info *sfu_info; + int ret = 0; + + sfu_info = kzalloc(sizeof(*sfu_info), GFP_KERNEL); + if (!sfu_info) + return -ENOMEM; + + sframe_unw_info_init(sfu_info, sframe_addr, sframe_size, text_addr, + text_size); + + if (sframe_unw_info_init_sfsec(sfstate, sfu_info)) { + ret = SFRAME_UNW_INVAL_SFRAME; + goto end; + } + + /* Add sframe_unw_info object for the program or its DSOs. */ + if (!sfstate->su_prog.sframe_size) + memcpy(&(sfstate->su_prog), sfu_info, sizeof(*sfu_info)); + else + ret = sframe_state_unw_info_list_add(sfstate, sfu_info); + +end: + kfree(sfu_info); + + return ret; +} + +/* + * Add SFrame unwind info for the given (prog or DSO) phdr_info into the SFrame + * state object in the task. + * + * Callback routine from iterate_phdr function. + * + * Returns 0 if success. + */ +static int add_sframe_unwind_info(struct phdr_info *info, + struct task_struct *task, + void *data) +{ + int err = 0; + int p_type; + struct sframe_state *sframe_state; + /* FIXME TODO what if its Elf32_Phdr. */ + Elf64_Phdr *phdr = NULL, *sframe_phdr = NULL, *text_phdr = NULL; + + uint64_t text_addr; + size_t text_size; + uint64_t sframe_addr; + size_t sframe_size; + + phdr = info->pi_phdr; + sframe_state = (struct sframe_state *)data; + + for (int j = 0; j < info->pi_phnum; j++) { + p_type = phdr[j].p_type; + /* Find the executable section and the SFrame section. */ + if (p_type == PT_GNU_SFRAME) { + sframe_phdr = &phdr[j]; + continue; + } + /* FIXME TODO Elf 101 - there be only one PF_X. Looks like it? */ + if (p_type == PT_LOAD && phdr[j].p_flags & PF_X) { + /* + * This is the executable part of the ELF binary + * containing the instructions, and may contain + * sections other than .text. The usage of `text` in + * this function is colloquial. + */ + text_phdr = &phdr[j]; + continue; + } + + if (sframe_phdr && text_phdr) + break; + } + + /* + * If there is no SFrame section for the prog, SFrame based unwinding + * should not be attempted. If no SFrame section is found for a DSO, + * however, it may still be possible to generate useful stacktraces + * using the SFrame sections' for other parts of the program. + */ + if (!sframe_phdr) + return info->pi_prog + ? SFRAME_UNW_NO_PROG_SFRAME + : SFRAME_UNW_PARTIAL_INFO; + + text_addr = info->pi_prog ? text_phdr->p_vaddr + : info->pi_addr + text_phdr->p_vaddr; + text_size = text_phdr->p_memsz; + sframe_addr = info->pi_prog ? sframe_phdr->p_vaddr + : info->pi_addr + sframe_phdr->p_vaddr; + sframe_size = sframe_phdr->p_memsz; + + /* Add the SFrame unwind info object to the list. */ + err = sframe_state_add_unw_info(sframe_state, sframe_addr, + sframe_size, text_addr, text_size); + /* + * An error indicates SFrame unwind info addition failed, but one can + * still unwind using .sframe for other parts of the program. + */ + if (err) + return SFRAME_UNW_PARTIAL_INFO; + + return SFRAME_UNW_INFO_OK; +} + +static int add_sframe_unwind_info_for_task(struct task_struct *task) +{ + struct sframe_state *sfstate = task->sframe_state; + + /* sfstate should be already allocated. */ + if (!sfstate) + return SFRAME_UNW_NO_SFSTATE; + + return iterate_phdr(add_sframe_unwind_info, task, sfstate); +} + +/* + * SFrame Unwind Info APIs. + */ + +struct sframe_unw_info *sframe_state_find_unw_info(struct sframe_state *sfstate, + uint64_t addr) +{ + struct sframe_unw_info_list *unw_info_list; + struct sframe_unw_info sfu_info; + int i; + + if (!sfstate) + return NULL; + + if (sfstate->su_prog.text_addr < addr + && sfstate->su_prog.text_addr + sfstate->su_prog.text_size > addr) + return &sfstate->su_prog; + + unw_info_list = &sfstate->su_dsos; + for (i = 0; i < unw_info_list->used; ++i) { + sfu_info = unw_info_list->entry[i]; + if ((sfu_info.text_addr <= addr) + && (sfu_info.text_addr + sfu_info.text_size >= addr)) + return &unw_info_list->entry[i]; + } + + return NULL; +} + +struct sframe_sec *sframe_unw_info_get_sfsec(struct sframe_unw_info *sfu_info) +{ + if (!sfu_info || !sfu_info->sfsec) + return NULL; + + return sfu_info->sfsec; +} + +/* + * SFrame state APIs. + */ + +static void unwind_sframe_state_free(struct sframe_state *sfstate) +{ + struct sframe_unw_info_list *unw_info_list; + struct sframe_unw_info *sfu_info; + int i; + + if (!sfstate) + return; + + sfu_info = &(sfstate->su_prog); + sframe_unw_info_cleanup(sfu_info); + + unw_info_list = &sfstate->su_dsos; + for (i = 0; i < unw_info_list->used; ++i) { + sfu_info = &unw_info_list->entry[i]; + sframe_unw_info_cleanup(sfu_info); + } + + kfree(sfstate->su_dsos.entry); + sfstate->su_dsos.entry = NULL; + sfstate->su_dsos.alloced = 0; + sfstate->su_dsos.used = 0; + + sfstate->task = NULL; +} + +bool unwind_sframe_state_valid_p(struct sframe_state *sfstate) +{ + return (sfstate && sfstate->cond != SFSTATE_INVAL); +} + +bool unwind_sframe_state_ready_p(struct sframe_state *sfstate) +{ + return (sfstate && sfstate->cond == SFSTATE_READY); +} + +struct sframe_state *unwind_sframe_state_alloc(struct task_struct *task) +{ + struct sframe_state *sfstate = NULL; + /* + * Check if the task's SFrame unwind information needs to be set up. + */ + sfstate = task->sframe_state; + if (!sfstate) { + /* Free'd up in release_task(). */ + sfstate = kzalloc(sizeof(*sfstate), GFP_KERNEL); + if (!sfstate) + return NULL; + sfstate->cond = SFSTATE_ALLOCED; + task->sframe_state = sfstate; + } + + sfstate->task = task; + + return sfstate; +} + +void unwind_sframe_state_cleanup(struct task_struct *task) +{ + if (!task->sframe_state) + return; + + unwind_sframe_state_free(task->sframe_state); + kfree(task->sframe_state); + task->sframe_state = NULL; +} + +/* + * Update the SFrame unwind state object cached per task. + * + * Sets cond to SFSTATE_INVAL if any error. + * Sets cond to SFSTATE_READY if no error. + */ +int unwind_sframe_state_update(struct task_struct *task) +{ + struct sframe_state *sfstate = NULL; + int sferr = 0; + + sfstate = task->sframe_state; + if (sfstate->cond == SFSTATE_ALLOCED || sfstate->cond == SFSTATE_STALE) + sferr = add_sframe_unwind_info_for_task(task); + + sfstate->cond = (sferr < SFRAME_UNW_INFO_OK) ? SFSTATE_INVAL + : SFSTATE_READY; + + return sferr < SFRAME_UNW_INFO_OK; +} diff --git a/lib/sframe/sframe_state.h b/lib/sframe/sframe_state.h new file mode 100644 index 000000000000..e2f0251b30d5 --- /dev/null +++ b/lib/sframe/sframe_state.h @@ -0,0 +1,80 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2023, Oracle and/or its affiliates. + */ + +#ifndef SFRAME_STATE_H +#define SFRAME_STATE_H + +#include "sframe_read.h" + +/* + * SFrame unwind info of the program or a DSO. + */ +struct sframe_unw_info { + /* SFrame segment's virtual addr. */ + uint64_t sframe_addr; + /* SFrame segment's size. */ + uint64_t sframe_size; + /* + * Keep a reference to the pages and vma for the lifetime of this SFrame + * unwind info object. + */ + struct page **sframe_pages; + unsigned long sframe_npages; + /* Address of the vmap'd area that contains the .sframe section. */ + const char *sframe_vmap; + /* + * text_addr and text_size below are used only for looking up the + * associated SFrame section. See sframe_state_find_unw_info. + */ + /* Text segment's virtual addr. */ + uint64_t text_addr; + /* Text segment's size. */ + uint64_t text_size; + /* SFrame section contents. */ + struct sframe_sec *sfsec; +}; + +/* + * List of SFrame unwind info objects. + * Typically used to represent SFrame unwind info for set of shared libraries + * of a program. + */ +struct sframe_unw_info_list { + /* Entries allocated. */ + int alloced; + /* Entries used. */ + int used; + /* List of SFrame unwind info objects. */ + struct sframe_unw_info *entry; +}; + +enum sframe_state_code { + SFSTATE_READY = 0, /* SFrame unwind OK to use. */ + SFSTATE_INVAL = 1, /* SFrame unwind is invalid. */ + SFSTATE_ALLOCED = 2, /* SFrame unwind is alloc'd but not initialized. */ + SFSTATE_STALE = 3, /* SFrame unwind is stale and not OK to use. */ +}; + +/* + * Per task SFrame unwind state. + */ +struct sframe_state { + /* The task that this SFrame unwind info belongs to. */ + struct task_struct *task; + /* Current SFrame state condition. */ + enum sframe_state_code cond; + /* SFrame stack trace info for program. */ + struct sframe_unw_info su_prog; + /* SFrame stack trace info for the shared objects. */ + struct sframe_unw_info_list su_dsos; +}; + +extern struct sframe_unw_info * +sframe_state_find_unw_info(struct sframe_state *sfstate, uint64_t addr); + +extern struct sframe_sec * +sframe_unw_info_get_sfsec(struct sframe_unw_info *sfu_info); + +#endif /* SFRAME_STATE_H. */ diff --git a/lib/sframe/sframe_unwind.c b/lib/sframe/sframe_unwind.c new file mode 100644 index 000000000000..32716008d0b4 --- /dev/null +++ b/lib/sframe/sframe_unwind.c @@ -0,0 +1,208 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2023, Oracle and/or its affiliates. + */ + +#include +#include +#include + +#include "sframe_state.h" + +/* + * Check if ADDR is in text segment (for which we also found a corresponding + * SFrame section. The address is considered invalid, if it is not in any of + * the address ranges of the text segments of either the main program or any of + * it's DSOs (for which a corresponding SFrame section existed). + * Return true if valid, false otherwise. + */ +static bool unwind_sframe_ip_ok(struct sframe_state *sfstate, uint64_t addr) +{ + return (sframe_state_find_unw_info(sfstate, addr) != NULL); +} + +void sframe_unwind_start(struct user_unwind_state *user_unw_state, + struct task_struct *task, struct pt_regs *regs) +{ + if (!task->sframe_state + || !unwind_sframe_state_ready_p(task->sframe_state)) + goto error; + + if (!regs) + goto error; + + user_unw_state->sp = get_ptregs_sp(regs); + user_unw_state->pc = get_ptregs_ip(regs); + user_unw_state->fp = get_ptregs_fp(regs); + + user_unw_state->task = task; + user_unw_state->stype = STACK_TYPE_TASK; + + /* We need to skip ahead by one. */ + sframe_unwind_next_frame(user_unw_state); + return; +error: + user_unw_state->error = true; +} + +bool sframe_unwind_next_frame(struct user_unwind_state *ustate) +{ + struct sframe_unw_info *sfu_info; + struct sframe_sec *sfsec; + struct task_struct *task; + struct sframe_state *sfstate; + + int32_t ra_offset, rfp_offset, cfa_offset; + + uint64_t cfa; + uint64_t return_addr; + uint64_t rfp_stack_loc; + uint64_t ra_stack_loc; + uint64_t sframe_vma; + + uint64_t pc = ustate->pc; + uint64_t sp = ustate->sp; + uint64_t fp = ustate->fp; + + struct sframe_fre fre, *frep = &fre; + int err = 0; + + task = ustate->task; + sfstate = task->sframe_state; + /* + * Indicate end of stack trace when SFrame unwind info + * is not found for the given PC. + */ + sfu_info = sframe_state_find_unw_info(sfstate, pc); + + if (!sfu_info) + goto the_end; + + sfsec = sframe_unw_info_get_sfsec(sfu_info); + + /* Find the SFrame FRE. */ + sframe_vma = sfu_info->sframe_addr; + pc -= sframe_vma; + err = sframe_sec_find_fre(sfsec, pc, frep); + + if (err != 0) + goto error; + + /* Get the CFA offset from the FRE. */ + cfa_offset = sframe_fre_get_cfa_offset(sfsec, frep, &err); + if (err == SFRAME_ERR_FREOFFSET_NOPRESENT) + goto error; + cfa = ((sframe_fre_get_base_reg_id(frep, &err) + == SFRAME_BASE_REG_SP) ? sp : fp) + cfa_offset; + + /* Get the RA offset from the FRE. */ + ra_offset = sframe_fre_get_ra_offset(sfsec, frep, &err); + if (err != 0) + goto error; + ra_stack_loc = cfa + ra_offset; + + if (!access_ok((const uint64_t __user *)ra_stack_loc, STACK_ACCESS_LEN)) + goto error; + + if (__get_user(return_addr, (const uint64_t __user *)ra_stack_loc)) + goto error; + + /* Get the FP offset from the FRE. */ + rfp_offset = sframe_fre_get_fp_offset(sfsec, frep, &err); + if (err == 0) { + rfp_stack_loc = cfa + rfp_offset; + + if (!access_ok((const uint64_t __user *) rfp_stack_loc, + STACK_ACCESS_LEN)) + goto error; + + if (__get_user(fp, (const uint64_t __user *)rfp_stack_loc)) + goto error; + } + + /* Validate and add return address to the list. */ + if (unwind_sframe_ip_ok(sfstate, return_addr)) { + ustate->pc = return_addr; + ustate->sp = cfa; + ustate->fp = fp; + + } else { + goto error; + } + + return true; + +error: + ustate->error = true; + return false; +the_end: + ustate->stype = STACK_TYPE_UNKNOWN; + return false; +} + +uint64_t sframe_unwind_get_return_address(struct user_unwind_state *state) +{ + return state->pc; +} + +/* + * Generate stack trace using SFrame stack trace information. + * Return 0 if success, 1 otherwise. + */ + +static int do_sframe_unwind(struct task_struct *task, + struct sframe_state *sfstate, + struct perf_callchain_entry_ctx *entry, + struct pt_regs *regs) +{ + struct user_unwind_state ustate; + uint64_t addr; + + memset(&ustate, 0, sizeof(ustate)); + + for (sframe_unwind_start(&ustate, task, regs); + !sframe_unwind_done(&ustate) && !sframe_unwind_error(&ustate); + sframe_unwind_next_frame(&ustate)) { + addr = sframe_unwind_get_return_address(&ustate); + if (!addr || perf_callchain_store(entry, addr)) + break; + } + + return 0; +} + +/* + * Get the stack trace for the task using SFrame stack trace information. + * Returns 0 if success, 1 otherwise. + */ + +int sframe_callchain_user(struct task_struct *task, + struct perf_callchain_entry_ctx *entry, + struct pt_regs *regs) +{ + int ret = 0; + struct sframe_state *sfstate; + + if (task != current) + return 1; + + /* Get the current task's sframe state. */ + sfstate = task->sframe_state; + + if (!sfstate) + return 1; + + /* + * Prepare for stack tracing. State must be SFSTATE_READY at this time. + * FIXME TODO SFrame state may be stale because there was a change in + * the set of DSOs used by the program, for example. + * FIXME TODO Need to update task->sframe_state if program + * dlopen/dlclose a DSO. + */ + if (ret || !unwind_sframe_state_ready_p(sfstate)) + return 1; + + ret = do_sframe_unwind(task, sfstate, entry, regs); + + return ret; +} -- 2.39.2