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 BA779C77B7A for ; Fri, 26 May 2023 05:32:54 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231495AbjEZFcx (ORCPT ); Fri, 26 May 2023 01:32:53 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:42836 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230013AbjEZFcv (ORCPT ); Fri, 26 May 2023 01:32:51 -0400 Received: from mx0a-00069f02.pphosted.com (mx0a-00069f02.pphosted.com [205.220.165.32]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id C06561B0 for ; Thu, 25 May 2023 22:32:42 -0700 (PDT) Received: from pps.filterd (m0246617.ppops.net [127.0.0.1]) by mx0b-00069f02.pphosted.com (8.17.1.19/8.17.1.19) with ESMTP id 34Q4iv1P029358; Fri, 26 May 2023 05:32:38 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=6CQQxNFkVs93Zs8k5sQC7tkGlosD3Glf5qtYBQ3BTAg=; b=Ov0VHgj/204JIVHkAfzguJZ641Ym4tygrw06aVRyI52MQIbmWbwnEfmPgALaSN/2ahmP hvthtvS8qOTqBQyYnr1VwkeCj4Emr7sMVhAHL5FDW4yK3r8DjIEdJC8N76Oq+7GLSNA6 +Y8IpQ4zm+l/FTN+TK10g5RcxGBq29PdlY37BYBsDiiyhJLnmjsvGOJRJbv0nnyvHhSj n2T7OIjSjeqTPUa91lP0K24yrdcqAb+RLMmsxFJJ/UDprsztcQu/5vIOPmShheOlv1G0 IG8+VodcRJETXBdoUKQrGIzfPXG49siLgF1mFGN45ne4xtrzndmCJhPrSqaFy7vv/39T jA== Received: from phxpaimrmta03.imrmtpd1.prodappphxaev1.oraclevcn.com (phxpaimrmta03.appoci.oracle.com [138.1.37.129]) by mx0b-00069f02.pphosted.com (PPS) with ESMTPS id 3qtp0qr1xy-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=OK); Fri, 26 May 2023 05:32:38 +0000 Received: from pps.filterd (phxpaimrmta03.imrmtpd1.prodappphxaev1.oraclevcn.com [127.0.0.1]) by phxpaimrmta03.imrmtpd1.prodappphxaev1.oraclevcn.com (8.17.1.19/8.17.1.19) with ESMTP id 34Q4peH2028528; Fri, 26 May 2023 05:32:37 GMT Received: from nam10-mw2-obe.outbound.protection.outlook.com (mail-mw2nam10lp2106.outbound.protection.outlook.com [104.47.55.106]) by phxpaimrmta03.imrmtpd1.prodappphxaev1.oraclevcn.com (PPS) with ESMTPS id 3qqk2ushda-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=OK); Fri, 26 May 2023 05:32:37 +0000 ARC-Seal: i=1; a=rsa-sha256; s=arcselector9901; d=microsoft.com; cv=none; b=E4wSi3nfybMQlfFLNVy7SFr1mGTElyzCca9R4Nua3IuECqlbOx5eJ8KDxdhZQTlP/rFF/2+jojKlR8Opmed/CR5d54EXXD50by/znC4xCB3B9atdx/gT3Vwn76xBaCPEbiO6WcYDr1l8vIQzjziMLoAUB9Ng17dw2i1ttuvtoPSwfI0wQ01Naap+07lNWY3UOGsL6EjpMNuywbv0OWNoc73jVzzs4zK71gAGcNh5a/FS3CVV5noG/X6b3if6r30NnG9QNiP7nBfThR3bPCTGkUFgSYqVJBAPd/kMQDIw5Mn6o6QvutYDWaPwbpQqNYZuX1zC2VgrOJGtH6OZwtr5Og== 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=6CQQxNFkVs93Zs8k5sQC7tkGlosD3Glf5qtYBQ3BTAg=; b=WXmENjNb2jn/KxgjIgEYjGxPVRjE+SABlCY1ZkLlY65FDaFjV9LG7yUSXvT4u0k/SuqB6m6C4nLJ0l4V7RpZU2vUCCUI33AOEC/a5qQXskNEWVr03Rhge+qa+G7U+zkpRiCrV21kutvSp1ckSxEnpf/KJQEVZk1hat1PFPJ0MhFs0iPMlvq4QKgqZpZ5FrrURnr5isltp0mbufF9YEXTB3EXMVn/QiHp+kNJosjyxnsEh9qUDXoHqNNHNlRa1oF1AU2IHoOyJRsd7ITjI9DePIj+pjb/COoPcgVBsaCMbMS8OUl8byMp95mxzeqLdaDtcyQnkgitiXBiywyelZwrHg== 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=6CQQxNFkVs93Zs8k5sQC7tkGlosD3Glf5qtYBQ3BTAg=; b=K1jhzHfZTnJQLLNO5YG4PYVjFFUbXy08blDtR2464ISBqajhNf3rsa59a4jxluZajMQy1TuqtZPKVLjOmRnMXIosz+laqKVL08TDpvvGI7EXpNxdX+sddFs0smI0nWwS+og8WKNCCzYKkrQ7q7qkwRJnUg/ryJ0U99L3s9ndefI= Received: from MWHPR1001MB2158.namprd10.prod.outlook.com (2603:10b6:301:2d::17) by MN2PR10MB4288.namprd10.prod.outlook.com (2603:10b6:208:1dc::7) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.6411.28; Fri, 26 May 2023 05:32:34 +0000 Received: from MWHPR1001MB2158.namprd10.prod.outlook.com ([fe80::fdbb:b921:1ef:2d44]) by MWHPR1001MB2158.namprd10.prod.outlook.com ([fe80::fdbb:b921:1ef:2d44%4]) with mapi id 15.20.6411.025; Fri, 26 May 2023 05:32:34 +0000 From: Indu Bhagat To: linux-toolchains@vger.kernel.org, rostedt@goodmis.org, peterz@infradead.org Cc: Indu Bhagat Subject: [POC,V2 4/5] sframe: add an SFrame format stack tracer Date: Thu, 25 May 2023 22:32:14 -0700 Message-Id: <20230526053215.3617580-5-indu.bhagat@oracle.com> X-Mailer: git-send-email 2.39.2 In-Reply-To: <20230526053215.3617580-1-indu.bhagat@oracle.com> References: <20230526053215.3617580-1-indu.bhagat@oracle.com> Content-Transfer-Encoding: 8bit Content-Type: text/plain X-ClientProxiedBy: MW4PR04CA0099.namprd04.prod.outlook.com (2603:10b6:303:83::14) To MWHPR1001MB2158.namprd10.prod.outlook.com (2603:10b6:301:2d::17) MIME-Version: 1.0 X-MS-PublicTrafficType: Email X-MS-TrafficTypeDiagnostic: MWHPR1001MB2158:EE_|MN2PR10MB4288:EE_ X-MS-Office365-Filtering-Correlation-Id: 68ec8793-9714-4d3c-5d19-08db5daa9b9c X-MS-Exchange-SenderADCheck: 1 X-MS-Exchange-AntiSpam-Relay: 0 X-Microsoft-Antispam: BCL:0; X-Microsoft-Antispam-Message-Info: RxCWcHD2dbAeax0UFzhOLkJNct0apit5jMzqPIc7KjmjlYwITFTnmONifkY+ykLk693NppaOBLnz30naKELhFx/U5iS4QMqI5kepJjmFk3yNQkC78VvFw92oGxe7tqzEVMnANpv5CaOMML4VQmXd0HRoPOvJa8q0Uc+YbVpfup+RxFOd1b+FEZ2ICrV36QpUKQJINfM+/70yh7QRmy6dFGfrRTLGoYM5VO73PF30Vz1j0VU2jM34FLDkAu86sQ082gSIiWYX4AFAZafSVvXKbVxdXY8/nYiJPTHrTQRhaY6MsS28I5YeokaHJq44GwcHT7syrLFPvWvFzKMQdeNwXu5m9+SahlV5NDtOqzJuvT8dIBWbQrElU7Z0Eh2PGF7ZtxubiztUa7nlFlK1I+wmoi/zjij0Vr734aU6mq5Cq2LzXAuuiaiDA4qZ7UpgoetmklyWh8i5J8Hc/IIro/hTseotvnqIf9ov9ck7SYqXQ6Gmlu8nQGOJNU4L59bSImoJSyCsJKPp2ewl8hitQ1vhQUEyKk/pVNAmbXuiHPxFco9QuVluWlmN6zCxvyQJCsvP 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)(396003)(136003)(366004)(39860400002)(376002)(346002)(451199021)(38100700002)(83380400001)(6512007)(6506007)(1076003)(26005)(107886003)(44832011)(36756003)(2616005)(2906002)(186003)(30864003)(66556008)(316002)(4326008)(66476007)(66946007)(6666004)(41300700001)(478600001)(6486002)(8676002)(66899021)(86362001)(8936002)(5660300002);DIR:OUT;SFP:1101; X-MS-Exchange-AntiSpam-MessageData-ChunkCount: 1 X-MS-Exchange-AntiSpam-MessageData-0: =?us-ascii?Q?dyQn6V6J2LrGoZcbqWItNAdYcTh9Ip16S0/FVlv9ZMUhR3sPdi8k7qq8AeYB?= =?us-ascii?Q?/uNAnhs2Yg5g7Gr0M+oL+EMTD0SYoCVfxGTf2N59MQXEcTNkR6JK1rPB4XcI?= =?us-ascii?Q?a+HpyH2UArXS+X7FN7UKQX7BQvcI0erGXkuRfst2/k7QOL9vQJCX0DfOKCMD?= =?us-ascii?Q?5BoHwPI0jnB3H2e5aRt41hjH86NaVZYoMCd4tPtr8x7te3GzH2zVAusPvvXu?= =?us-ascii?Q?Leylf6lznCIc11Q5JuVh2h9lfeNeqShq2JLQXkDxWjeLQl/uUPzAhPgP4J6v?= =?us-ascii?Q?rIXABs+7mFYcbQwqZaDQjXPobspqiUyO0quZGIOcMsFwGA46/2c0UbVd5VQq?= =?us-ascii?Q?HD8OeiFICuROjICNb6SJt0+eSAdBfLVi0K2emELA7V7z6V7nr6ecjvPoz7Yr?= =?us-ascii?Q?x/CnyfjzBm0dU8pBTnWuNr+OL+C1qKnYFAqc4K6OFvK6hUxD7M9/3yXGPZsj?= =?us-ascii?Q?KR252RjID6iasw234QU23kOl3idWIAtmVN04yefOCLRlcc2kx6QhXd7DXjLM?= =?us-ascii?Q?7NtWCY2Rt1Kgk/h4p/GOVKtBJWodI1jaMUlDJg3dyrv91aa+V/F50EKOGGbh?= =?us-ascii?Q?CC35W1a4xLG16WZD204m4skQB16f6/d5Lq35z/1VMI1t7EGmBuarOltLSL+V?= =?us-ascii?Q?AN9ZC97HsjRsJ8Fyi8ISOwWiPAPAAFELxcCw74ASCUiom+XefHTyNSuqr21l?= =?us-ascii?Q?kYtTU5DtnGLsHe+NWiUDFNk07wvcnGVxez5kmTyPIbEdW6nWJgXzwr4SADee?= =?us-ascii?Q?L04kABG/cuONg7U+mwxsiQsJg0Kl3w7m/N5QrACGBRCqIQlTMZmK157SFhHC?= =?us-ascii?Q?RZwsU9Smx6YmyBLAOwggFHhnmRGnlUM51RpCivbZ2r0ivYt6WbC27SQoZNZv?= =?us-ascii?Q?gjHhbEKqXUtTQrUKXec4Jy5iiCUnKC8xl5k+cw8r92EohCUoV2yMgLARkA3l?= =?us-ascii?Q?iMNVO0a0K0F00NaBUw854VTGGxP19CIZM5mQ99bEv1gp5gX+z7cX7Sv9UjYP?= =?us-ascii?Q?0sDBNykiHnjYz61vqueMdILAjs/UvUScolOBH7mw8hR5xZRrmQuOK5BdQLSN?= =?us-ascii?Q?dR9hNIm+JrCdo/tncvXQGXiETVseUVxFMwFS0s3WOPw24Y2GZSPlBvstwJ8g?= =?us-ascii?Q?ynkNxIJ80zOH4luX4xSLD0Z38l77dG16p7CujM64OB9fDOgh0W56mF5L+YqX?= =?us-ascii?Q?Y0ksdTKJZLbpZvM0vK8wFBFljzxDyYXz8uXRal/rgLbLJXC1YQzGVinGx+AJ?= =?us-ascii?Q?jHLHtHXzs2bCRpnl+KhBwOSlyDXhEqC2yeoqatWTIqCcBI4L7IZeSrKBwDwF?= =?us-ascii?Q?ToKea+e9DBBQuHEzjfAkpAwHh/Elp3AnknFgjv0lHh+72oemrrYMDHMp1lEo?= =?us-ascii?Q?Cir9hMsUFX6NB8/kcVqmr58CcdOWYk5bDDmKONHWM9hK/fJFO1JubV8cRrnX?= =?us-ascii?Q?y44GTIJj2ejdGMTsv19pO6v6e9B/ZfJl6ePJw8ETESUyTSY3RTdjvmXeyFzP?= =?us-ascii?Q?TdtDGkw0OEbLCDGOs9Z+kJoZtSHOUbU4oJvzY9MegwbHDFFrRgxHVGzXIXOH?= =?us-ascii?Q?qHFVvFEKapSacKOiq0PugnBwVqaypVYUS7o+ycSn?= X-MS-Exchange-AntiSpam-ExternalHop-MessageData-ChunkCount: 1 X-MS-Exchange-AntiSpam-ExternalHop-MessageData-0: aGGdRskOreklPG6lRZTPJoKJd9njNi0Av7PeMk+Xojky+jdTMKI/d+1PKX0w+dpS/u1l3UTYEfFKISlc1vdQuWSp6v6ZTmwgRRWVUaaleWqBsA6yKb7AHGV6qe7ht3ERndIwFIFLIaS6tb5x6wItVTcauJK+M5B/Y2ZyQ0xNr6sfJvsmNL65v5GjvJpyfpXR2uldIpnBLGe21arL9mKRxYCRAgJMbUiXuKgCa2zUBq8yyW+Z4EJzITI55oDsACLgih9QxeFwC19wbnzi23ukHBD3SgG+71RiAMpKG+afx2ShLKBGfyRlyjj/M++VyE5UUxGggrL5TxR4BiXS/ko7/0Pg0G13ewuRABNavmTDTO6iI3SfYzpSnhZh83+UzfqHEAi/h4sFbmMUFaBlptKr2S2U1zTOUPtsMxqDOtY6oZv3UmdJVVzRvF7yYJ4ky7SkysDcMnlovMPk+KQKicLVgVo42xT6Z9Wr5GsEd+YMXjSK9qszhd34oGmfTr1Z2FsE21nSKTwctGM9lup3z1yPuC22+P8w/jPqsS2mTSgc21+RvykUMI5r4l0Eh4DwlEiaRfY7UDfKwZbC8pr08+93vnVktVpW359fnW6lSxc1koG3XUpVUQID19OwW79ePSmNRRcLVSNz3w/ahyBzM1hNoZs6sYFZYXtNzxjiPqQ+19xkmS9uiV0qNekgzJ3L3hKv0Dq+tNCGbVg8YGd53tRIahYV91P/1v4U2DXPRc35eXdth7FHNBcSIkB0N/tjLrMNA/pwT+A1T6u0eQIRMtaKfZ1o/dMh1MLE/ngw1MQ1htRYMi+/PvE33VmjL8hRkjIt0uX7Vroj+zv8krwbnhzLoQ== X-OriginatorOrg: oracle.com X-MS-Exchange-CrossTenant-Network-Message-Id: 68ec8793-9714-4d3c-5d19-08db5daa9b9c X-MS-Exchange-CrossTenant-AuthSource: MWHPR1001MB2158.namprd10.prod.outlook.com X-MS-Exchange-CrossTenant-AuthAs: Internal X-MS-Exchange-CrossTenant-OriginalArrivalTime: 26 May 2023 05:32:34.2011 (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: 7h56s5dbV1BjefzwfSqVgA0aHVKCeuVt42EI57diws5zOfeZMOPey7G3/0HUy/vRhsCXAVEnlKtUnaAhukWWAw== X-MS-Exchange-Transport-CrossTenantHeadersStamped: MN2PR10MB4288 X-Proofpoint-Virus-Version: vendor=baseguard engine=ICAP:2.0.254,Aquarius:18.0.957,Hydra:6.0.573,FMLib:17.11.176.26 definitions=2023-05-26_01,2023-05-25_03,2023-05-22_02 X-Proofpoint-Spam-Details: rule=notspam policy=default score=0 spamscore=0 malwarescore=0 adultscore=0 mlxscore=0 bulkscore=0 suspectscore=0 mlxlogscore=999 phishscore=0 classifier=spam adjust=0 reason=mlx scancount=1 engine=8.12.0-2304280000 definitions=main-2305260045 X-Proofpoint-GUID: kTdlNc3kKe3DUMjANQzDgQpE4xJQLLuW X-Proofpoint-ORIG-GUID: kTdlNc3kKe3DUMjANQzDgQpE4xJQLLuW Precedence: bulk List-ID: X-Mailing-List: linux-toolchains@vger.kernel.org [Changes in V2] - Formatting related fixes - kernel sytle code and comments etc. - Use abstractions from ptrace.h. Get rid of arch-specific sframe_regs.h. - Use const char * consistently. - More xmas trees. - Move sframe_unwind.h from include/sframe/ to include/linux/. - Use consistent documentation style for all structures. [End of changes in V2] 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 pulled in from arch/arm64/include/asm/ptrace.h and arch/x86/include/asm/ptrace.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.) will need to be adapted to use more appropriate access method. There are more aspects than above; The intention of this patch set is to help the initiative to best incorporate an SFrame-based user space unwinder in the kernel. Signed-off-by: Indu Bhagat --- include/linux/sframe_unwind.h | 72 ++++++ lib/sframe/Makefile | 8 +- lib/sframe/iterate_phdr.c | 115 +++++++++ lib/sframe/iterate_phdr.h | 39 +++ lib/sframe/sframe_state.c | 447 ++++++++++++++++++++++++++++++++++ lib/sframe/sframe_state.h | 84 +++++++ lib/sframe/sframe_unwind.c | 214 ++++++++++++++++ 7 files changed, 978 insertions(+), 1 deletion(-) create mode 100644 include/linux/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/include/linux/sframe_unwind.h b/include/linux/sframe_unwind.h new file mode 100644 index 000000000000..d94f44d18400 --- /dev/null +++ b/include/linux/sframe_unwind.h @@ -0,0 +1,72 @@ +/* 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 + +/** + * struct user_unwind_state - User space stack tracing state. + * @pc: Program counter. + * @sp: Stack pointer. + * @fp: Frame pointer. + * @ra: Return address. + * @task: Reference of the user space task. + * @stype: Stack type. + * @error: Error code. + */ +struct user_unwind_state { + uint64_t pc; + uint64_t sp; + uint64_t fp; + uint64_t ra; + struct task_struct *task; + enum stack_type stype; + 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 14d6cfd42a1d..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..c638ff5443ee --- /dev/null +++ b/lib/sframe/iterate_phdr.c @@ -0,0 +1,115 @@ +// 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 vm_area_struct *vma_mt; + struct phdr_info phinfo; + struct mm_struct *mm; + struct page *page; + bool first = true; + Elf64_Ehdr *ehdr; + size_t size; + int res = 0; + int err; + int ret; + + 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 + * greater than 1 page. + */ + + /* + * FIXME TODO KERNEL + * - get_user_pages_WHAT, which API. What flags ? + */ + 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 = ((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 ? + */ + size = sizeof(Elf64_Ehdr) + ehdr->e_phentsize * ehdr->e_phnum; + if (size > 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..44c75e43f5a7 --- /dev/null +++ b/lib/sframe/iterate_phdr.h @@ -0,0 +1,39 @@ +/* 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 - Helper object for iterate_phdr API + * @pi_prog: Determine whether prog or DSO. + * @pi_addr: Base address. + * @pi_phdr: Reference to the ELF program headers of the object. + * @pi_phnum: Number of entries in the program header table. + * @pi_adds: Number of shared objects added after program startup. + * @pi_subs: Number of shared objects removed after program startup. + */ +struct phdr_info { + bool pi_prog; + unsigned long pi_addr; + void *pi_phdr; + 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..e2f621f2464c --- /dev/null +++ b/lib/sframe/sframe_state.c @@ -0,0 +1,447 @@ +// 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, +}; + +/* + * SFrame Unwind Info APIs. + */ + +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) +{ + struct vm_area_struct *vma; + struct task_struct *task; + struct sframe_sec *sfsec; + const char *sframe_vmap; + const char *sframe_buf; + unsigned long npages; + struct page **pages; + int err = 0; + int i; + + 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 to 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) +{ + struct sframe_state *sframe_state; + /* FIXME TODO what if its Elf32_Phdr. */ + Elf64_Phdr *sframe_phdr = NULL; + Elf64_Phdr *text_phdr = NULL; + Elf64_Phdr *phdr = NULL; + + uint64_t sframe_addr; + size_t sframe_size; + uint64_t text_addr; + size_t text_size; + + int err = 0; + int p_type; + + 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) { + if (info->pi_prog) + return SFRAME_UNW_NO_PROG_SFRAME; + else + return 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); +} + +static bool sframe_unw_info_text_range_p(struct sframe_unw_info *sfu_info, + uint64_t addr) +{ + bool in_range = false; + + if (!sfu_info) + return false; + + if ((sfu_info->text_addr <= addr) && + (sfu_info->text_addr + sfu_info->text_size >= addr)) + in_range = true; + + return in_range; +} + +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 (sframe_unw_info_text_range_p(&sfstate->su_prog, 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 (sframe_unw_info_text_range_p(&sfu_info, 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; + int sferr = 0; + bool ret; + + sfstate = task->sframe_state; + if (sfstate->cond == SFSTATE_ALLOCED || sfstate->cond == SFSTATE_STALE) + sferr = add_sframe_unwind_info_for_task(task); + + ret = (sferr < SFRAME_UNW_INFO_OK); + if (ret) + sfstate->cond = SFSTATE_INVAL; + else + sfstate->cond = SFSTATE_READY; + + return ret; +} diff --git a/lib/sframe/sframe_state.h b/lib/sframe/sframe_state.h new file mode 100644 index 000000000000..abdd279c6597 --- /dev/null +++ b/lib/sframe/sframe_state.h @@ -0,0 +1,84 @@ +/* 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" + +/** + * struct sframe_unw_info - SFrame unwind info of the program or a DSO. + * @sframe_addr: SFrame segment's virtual addr. + * @sframe_size: SFrame segment's size. + * @sframe_pages: Pages containing the SFrame section. + * @sframe_npages: Number of pages containing the SFrame section. + * @sframe_vmap: Address of the vmap'd area that contains the .sframe section. + * @text_addr: Text segment's virtual addr. + * @text_size: Text segment's size. + * @sfsec: SFrame section contents. + */ +struct sframe_unw_info { + uint64_t sframe_addr; + 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; + 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. + */ + uint64_t text_addr; + uint64_t text_size; + + struct sframe_sec *sfsec; +}; + +/** + * struct sframe_unw_info_list - List of SFrame unwind info objects. + * @alloced: Number of entries allocated. + * @used: Number of entries used. + * @entry: Array 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 { + int alloced; + int used; + 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. */ +}; + +/** + * struct sframe_state - Per task SFrame unwind state. + * @task: The task that this SFrame unwind info belongs to. + * @cond: Current condition of the SFrame state. + * @su_prog: SFrame unwind info object for the program. + * @su_dsos: SFrame unwind info list for the shared objects. + */ +struct sframe_state { + struct task_struct *task; + enum sframe_state_code cond; + struct sframe_unw_info su_prog; + 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..56d579e18034 --- /dev/null +++ b/lib/sframe/sframe_unwind.c @@ -0,0 +1,214 @@ +// 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 = user_stack_pointer(regs); + user_unw_state->pc = instruction_pointer(regs); + user_unw_state->fp = frame_pointer(regs); + +#ifdef procedure_link_pointer + user_unw_state->ra = procedure_link_pointer(regs); +#else + user_unw_state->ra = 0; +#endif + + 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_state *sfstate; + struct sframe_sec *sfsec; + struct task_struct *task; + struct sframe_fre *frep; + struct sframe_fre fre; + + int32_t ra_offset; + int32_t rfp_offset; + int32_t cfa_offset; + + uint64_t rfp_stack_loc; + uint64_t ra_stack_loc; + uint64_t sframe_vma; + uint64_t cfa; + int err = 0; + + uint64_t pc; + uint64_t sp; + uint64_t fp; + uint64_t ra; + + pc = ustate->pc; + sp = ustate->sp; + fp = ustate->fp; + ra = ustate->ra; + + frep = &fre; + + 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) { + ra_stack_loc = cfa + ra_offset; + if (__get_user(ra, (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 (__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, ra)) { + ustate->pc = ra; + 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) +{ + struct sframe_state *sfstate; + int ret; + + 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 (!unwind_sframe_state_ready_p(sfstate)) + return 1; + + ret = do_sframe_unwind(task, sfstate, entry, regs); + + return ret; +} -- 2.39.2