Received: by 2002:a05:7412:37c9:b0:e2:908c:2ebd with SMTP id jz9csp2249217rdb; Thu, 21 Sep 2023 12:50:39 -0700 (PDT) X-Google-Smtp-Source: AGHT+IHhjULTMjZ0qz15UqEWSG/MeLK6egSWN8Lri1TcJq+jiz+Ec1DVbocMWj0xB9l8WMDw413M X-Received: by 2002:a05:6a00:2d84:b0:68a:6cbe:35a7 with SMTP id fb4-20020a056a002d8400b0068a6cbe35a7mr6471243pfb.2.1695325838648; Thu, 21 Sep 2023 12:50:38 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1695325838; cv=none; d=google.com; s=arc-20160816; b=mI12fqtMunW9n7O+uZC9i/0rwubDbUEQhhAR6LxOW2Sw/5h5OHdSRnjviEHvMd/TEi 2EvpP+PQIPdU6PXEuW6Hq3RWIyp6n6tFuLyywgmzh0GznT8LrUnLMsnydmr4cWzjJjN6 SihK8lfO3L94VIRkH1VCm4/as61+S7naBbsW7FLj+Ul3G1KpP+yPmi8w1MBpb+BWMj7Z P9EhT3Bhh66HG3dwxRTWzyn2MMTgpmMawUUMmAGjtHCFIkOhT4+zbynSs5Od5wV5aoUE 61UnCzeaVGkS9sTM46oUWTjHGg273eFQ9qdkwck+0z73WmQFncCNNBAVCO+Jpk31bvd9 UP/Q== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:mime-version:message-id:date:references :in-reply-to:subject:to:from:dkim-signature; bh=0MDeOWlknUyZkJsutuhLisA36dk2gCYDOP+m7VPTz8A=; fh=pwXgmIfAZOh4LTwp5srhXtWSOiozIHPfmj/aKHlBwlA=; b=Bh/1X8h3cFyJ9ZWYESmAqTavZduRvNq6fXhMu2lmF5h9SOr2Y/BKRhNLda8z9io3BC ZqlM40l9/R7secu0Wk0qP3L7Qc+p4VEKH++h8PZ7EvzifzLRxdhSklsbVkjk68+Tj/V0 8o9UDEfH6i/XyO1f1biZiak+2jsqkVPCdUUHTj0KSq3KD2HXkT96ieFQdauRhh8rGZkL EgXu9Dze/5mDD4C9cdDhIxr+zkUhcPgM+2xfEx8yzLOR82/U01fjdFxwMt9PXr3gkzbH QaBt5YK7iQR8IVxl5ja5kjOBZtCSuYs2XHgzlwadI8GyfowEglkVSN2eBWSJnpi6tJZh 4g6Q== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@gmail.com header.s=20230601 header.b=bdPdNcPB; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 23.128.96.37 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=QUARANTINE dis=NONE) header.from=gmail.com Return-Path: Received: from snail.vger.email (snail.vger.email. [23.128.96.37]) by mx.google.com with ESMTPS id fo10-20020a056a00600a00b0068fa57cc15bsi2116330pfb.124.2023.09.21.12.50.37 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 21 Sep 2023 12:50:38 -0700 (PDT) Received-SPF: pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 23.128.96.37 as permitted sender) client-ip=23.128.96.37; Authentication-Results: mx.google.com; dkim=pass header.i=@gmail.com header.s=20230601 header.b=bdPdNcPB; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 23.128.96.37 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=QUARANTINE dis=NONE) header.from=gmail.com Received: from out1.vger.email (depot.vger.email [IPv6:2620:137:e000::3:0]) by snail.vger.email (Postfix) with ESMTP id 46F5C81890BF; Thu, 21 Sep 2023 10:50:10 -0700 (PDT) X-Virus-Status: Clean X-Virus-Scanned: clamav-milter 0.103.10 at snail.vger.email Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229972AbjIURuI (ORCPT + 99 others); Thu, 21 Sep 2023 13:50:08 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:49130 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229842AbjIURtw (ORCPT ); Thu, 21 Sep 2023 13:49:52 -0400 Received: from mail-wm1-x332.google.com (mail-wm1-x332.google.com [IPv6:2a00:1450:4864:20::332]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 0793D51587; Thu, 21 Sep 2023 10:15:36 -0700 (PDT) Received: by mail-wm1-x332.google.com with SMTP id 5b1f17b1804b1-405361bb9cdso7821675e9.0; Thu, 21 Sep 2023 10:15:36 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1695316534; x=1695921334; darn=vger.kernel.org; h=mime-version:message-id:date:references:in-reply-to:subject:to:from :from:to:cc:subject:date:message-id:reply-to; bh=0MDeOWlknUyZkJsutuhLisA36dk2gCYDOP+m7VPTz8A=; b=bdPdNcPB6+MbDonidstloCcmUgUqaJ8A59Gh/T5jrWYICQodXmiuaMRLxSMNaJIzPk F4UqSR7YBIP0KVyeEbTL/Y7uVlJgfg3VU10ZpgxgaeqZhyZwrUgSAYTfitrJ/Imipbse 83qeEMDnK6xC1Td6Hg5bgW4xFt7Z4jB+fl407oiNthIOPKKMf7KO/9qfMmbioGhoh9s8 ef9H+35Kw4Tb3/5A1d6wmsKnMyNK3w9KfBl6C6shob63v3CmXFixrbHYUfNqW0MVNQaU 7BKGD7qkJnMQzxPoHUi7Wu5lf1hp+vt+HCZICzt2Y57ZPBEdlXYkbXbgBKlRg3krcAp1 3Htg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1695316534; x=1695921334; h=mime-version:message-id:date:references:in-reply-to:subject:to:from :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to; bh=0MDeOWlknUyZkJsutuhLisA36dk2gCYDOP+m7VPTz8A=; b=WC8GOOC5DCiC8QyYR4Cdjc3Dv1UVw3qdJWittbA3z/vXLlzYN9E6pTbGBWwVFGcYaw IeugJp4ANQw4qIzdT2rwSGjb0x73aeBe0HDGoKlmG4bt4R9Xg1HEdXOHcx/cGUaqtWdc ZocO/1D0rr/fUsDaFvY2LW+3m3qBH32CCD2tlRYxYk3rQPMc3tcXo/3DnM8x4jKC8t3A bTyJzBp9jTNG0yZxkMfJgHHm69GcRdBjcGom/W+cSNb91Xp1M2D9GrxUnUePK6rDb3xC NKnCXqM1M8P/2Bl3o8d43Kz7Cn8QGQIYNWzNarZJCDsD2pb+UO3VAmRgCYzdqbRPoE9/ RDyQ== X-Gm-Message-State: AOJu0Yyh4LHCjhucrm9A1Q9gLRxNnR/tD9tWlbb1u/Kfxd5x5ZY/Ho0I kpB/GqTB1sE10yDCZHXkhrWdMaLvz4DQQC0f0mw= X-Received: by 2002:a5d:6387:0:b0:320:93a:6273 with SMTP id p7-20020a5d6387000000b00320093a6273mr5219904wru.49.1695306522983; Thu, 21 Sep 2023 07:28:42 -0700 (PDT) Received: from localhost (54-240-197-231.amazon.com. [54.240.197.231]) by smtp.gmail.com with ESMTPSA id j2-20020a5d4642000000b00317e77106dbsm1894439wrs.48.2023.09.21.07.28.41 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Thu, 21 Sep 2023 07:28:42 -0700 (PDT) From: Puranjay Mohan To: Xu Kuohai , Alexei Starovoitov , Daniel Borkmann , Andrii Nakryiko , Martin KaFai Lau , Song Liu , Yonghong Song , John Fastabend , KP Singh , Stanislav Fomichev , Hao Luo , Jiri Olsa , Zi Shen Lim , Catalin Marinas , Will Deacon , bpf@vger.kernel.org, linux-arm-kernel@lists.infradead.org, linux-kernel@vger.kernel.org, Kumar Kartikeya Dwivedi Subject: Re: [PATCH bpf-next v2 1/1] bpf, arm64: support exceptions In-Reply-To: References: <20230917000045.56377-1-puranjay12@gmail.com> <20230917000045.56377-2-puranjay12@gmail.com> <041d4f6b-1350-105e-6ab0-73980aba26ea@huaweicloud.com> Date: Thu, 21 Sep 2023 14:28:39 +0000 Message-ID: MIME-Version: 1.0 Content-Type: text/plain X-Spam-Status: No, score=-1.8 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,FREEMAIL_ENVFROM_END_DIGIT, FREEMAIL_FROM,RCVD_IN_DNSWL_BLOCKED,SPF_HELO_NONE,SPF_PASS autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on lindbergh.monkeyblade.net Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org X-Greylist: Sender passed SPF test, not delayed by milter-greylist-4.6.4 (snail.vger.email [0.0.0.0]); Thu, 21 Sep 2023 10:50:10 -0700 (PDT) Xu Kuohai writes: > On 9/21/2023 9:16 PM, Puranjay Mohan wrote: >> Xu Kuohai writes: >> >>> On 9/17/2023 8:00 AM, Puranjay Mohan wrote: >>>> Implement arch_bpf_stack_walk() for the ARM64 JIT. This will be used >>>> by bpf_throw() to unwind till the program marked as exception boundary and >>>> run the callback with the stack of the main program. >>>> >>>> The prologue generation code has been modified to make the callback >>>> program use the stack of the program marked as exception boundary where >>>> callee-saved registers are already pushed. >>>> >>>> As the bpf_throw function never returns, if it clobbers any callee-saved >>>> registers, they would remain clobbered. So, the prologue of the >>>> exception-boundary program is modified to push R23 and R24 as well, >>>> which the callback will then recover in its epilogue. >>>> >>>> The Procedure Call Standard for the Arm 64-bit Architecture[1] states >>>> that registers r19 to r28 should be saved by the callee. BPF programs on >>>> ARM64 already save all callee-saved registers except r23 and r24. This >>>> patch adds an instruction in prologue of the program to save these >>>> two registers and another instruction in the epilogue to recover them. >>>> >>>> These extra instructions are only added if bpf_throw() used. Otherwise >>>> the emitted prologue/epilogue remains unchanged. >>>> >>>> [1] https://github.com/ARM-software/abi-aa/blob/main/aapcs64/aapcs64.rst >>>> >>>> Signed-off-by: Puranjay Mohan >>>> --- >>>> arch/arm64/net/bpf_jit_comp.c | 98 ++++++++++++++++---- >>>> tools/testing/selftests/bpf/DENYLIST.aarch64 | 1 - >>>> 2 files changed, 79 insertions(+), 20 deletions(-) >>>> >>>> diff --git a/arch/arm64/net/bpf_jit_comp.c b/arch/arm64/net/bpf_jit_comp.c >>>> index 7d4af64e3982..fcc55e558863 100644 >>>> --- a/arch/arm64/net/bpf_jit_comp.c >>>> +++ b/arch/arm64/net/bpf_jit_comp.c >>>> @@ -21,6 +21,7 @@ >>>> #include >>>> #include >>>> #include >>>> +#include >>>> >>>> #include "bpf_jit.h" >>>> >>>> @@ -285,7 +286,7 @@ static bool is_lsi_offset(int offset, int scale) >>>> /* Tail call offset to jump into */ >>>> #define PROLOGUE_OFFSET (BTI_INSNS + 2 + PAC_INSNS + 8) >>>> >>>> -static int build_prologue(struct jit_ctx *ctx, bool ebpf_from_cbpf) >>>> +static int build_prologue(struct jit_ctx *ctx, bool ebpf_from_cbpf, bool is_exception_cb) >>>> { >>>> const struct bpf_prog *prog = ctx->prog; >>>> const bool is_main_prog = !bpf_is_subprog(prog); >>>> @@ -333,19 +334,28 @@ static int build_prologue(struct jit_ctx *ctx, bool ebpf_from_cbpf) >>>> emit(A64_MOV(1, A64_R(9), A64_LR), ctx); >>>> emit(A64_NOP, ctx); >>>> >>>> - /* Sign lr */ >>>> - if (IS_ENABLED(CONFIG_ARM64_PTR_AUTH_KERNEL)) >>>> - emit(A64_PACIASP, ctx); >>>> - >>>> - /* Save FP and LR registers to stay align with ARM64 AAPCS */ >>>> - emit(A64_PUSH(A64_FP, A64_LR, A64_SP), ctx); >>>> - emit(A64_MOV(1, A64_FP, A64_SP), ctx); >>>> - >>>> - /* Save callee-saved registers */ >>>> - emit(A64_PUSH(r6, r7, A64_SP), ctx); >>>> - emit(A64_PUSH(r8, r9, A64_SP), ctx); >>>> - emit(A64_PUSH(fp, tcc, A64_SP), ctx); >>>> - emit(A64_PUSH(fpb, A64_R(28), A64_SP), ctx); >>>> + if (!is_exception_cb) { >>>> + /* Sign lr */ >>>> + if (IS_ENABLED(CONFIG_ARM64_PTR_AUTH_KERNEL)) >>>> + emit(A64_PACIASP, ctx); >>>> + /* Save FP and LR registers to stay align with ARM64 AAPCS */ >>>> + emit(A64_PUSH(A64_FP, A64_LR, A64_SP), ctx); >>>> + emit(A64_MOV(1, A64_FP, A64_SP), ctx); >>>> + >>>> + /* Save callee-saved registers */ >>>> + emit(A64_PUSH(r6, r7, A64_SP), ctx); >>>> + emit(A64_PUSH(r8, r9, A64_SP), ctx); >>>> + emit(A64_PUSH(fp, tcc, A64_SP), ctx); >>>> + emit(A64_PUSH(fpb, A64_R(28), A64_SP), ctx); >>>> + } else { >>>> + /* Exception callback receives FP of Main Program as third parameter */ >>>> + emit(A64_MOV(1, A64_FP, A64_R(2)), ctx); >>>> + /* >>>> + * Main Program already pushed the frame record and the callee-saved registers. The >>>> + * exception callback will not push anything and re-use the main program's stack. >>>> + */ >>>> + emit(A64_SUB_I(1, A64_SP, A64_FP, 80), ctx); /* 10 registers are on the stack */ >>> >>> To ensure th calculated A6_SP is always correct, add an assertion >>> to ensure the distance between A64_FP and A64_SP is 80 after all >>> callee-registers are pushed to the stack? >>> >> >> I agree that this should be done. Can you give an example how this >> should be implemented? >> > > IIUC, bpf_throw is essentially a tail call to the exception boundary prog, so > can we reset the SP to the PROLOGUE_OFFSET position? If so, we can rely on the > assertion of PROLOGUE_OFFSET in build_prologue, or we can add a simliar assertion. > I will think more about this but the PROLOGUE_OFFSET assertion is about the number of emitted instructions, here we want to see how many registers are pushed on the stack. This can only be asseted at runtime. IMHO we don't need to add an assetion here because we are pushing 10 registers for every main program. >>>> + } >>>> >>>> /* Set up BPF prog stack base register */ >>>> emit(A64_MOV(1, fp, A64_SP), ctx); >>>> @@ -365,6 +375,13 @@ static int build_prologue(struct jit_ctx *ctx, bool ebpf_from_cbpf) >>>> emit_bti(A64_BTI_J, ctx); >>>> } >>>> >>>> + /* >>>> + * Program acting as exception boundary should save all ARM64 Callee-saved registers as the >>>> + * exception callback needs to recover all ARM64 Callee-saved registers in its epilogue. >>>> + */ >>>> + if (prog->aux->exception_boundary) >>>> + emit(A64_PUSH(A64_R(23), A64_R(24), A64_SP), ctx); >>> >>> Blindly storing x23/x24 to BPF_FP -8/16 is incorrect, as the stack >>> space below BPF_FP might be written with other values by the bpf >>> prog. >>> >> >> Thanks for pointing this out. I will set fp = A64_SP - 16 so to allocate >> space for saving x23/x24. And I will take care while poping back in the epilogue. >> >>>> + >>>> emit(A64_SUB_I(1, fpb, fp, ctx->fpb_offset), ctx); >>>> >>>> /* Stack must be multiples of 16B */ >>>> @@ -653,7 +670,7 @@ static void build_plt(struct jit_ctx *ctx) >>>> plt->target = (u64)&dummy_tramp; >>>> } >>>> >>>> -static void build_epilogue(struct jit_ctx *ctx) >>>> +static void build_epilogue(struct jit_ctx *ctx, bool is_exception_cb) >>>> { >>>> const u8 r0 = bpf2a64[BPF_REG_0]; >>>> const u8 r6 = bpf2a64[BPF_REG_6]; >>>> @@ -666,6 +683,14 @@ static void build_epilogue(struct jit_ctx *ctx) >>>> /* We're done with BPF stack */ >>>> emit(A64_ADD_I(1, A64_SP, A64_SP, ctx->stack_size), ctx); >>>> >>>> + /* >>>> + * Program acting as exception boundary pushes R23 and R24 in addition to BPF callee-saved >>>> + * registers. Exception callback uses the boundary program's stack frame, so recover these >>> >>> Keep the line width within 80 characters? >> >> bdc48fa11e46 ("checkpatch/coding-style: deprecate 80-column warning") >> removed the warning so I started using 100 character lines. >> > > 80-column is not a hard limit, but it's still preferred, right? > And I don't think it's a good idea to include two different line > width styles in a single file. Okay, I will change to 80 everywhere in the patch. > >>> >>>> + * extra registers in the above two cases. >>>> + */ >>>> + if (ctx->prog->aux->exception_boundary || is_exception_cb) >>>> + emit(A64_POP(A64_R(23), A64_R(24), A64_SP), ctx); >>>> + >>>> /* Restore x27 and x28 */ >>>> emit(A64_POP(fpb, A64_R(28), A64_SP), ctx); >>>> /* Restore fs (x25) and x26 */ >>>> @@ -1575,7 +1600,7 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog) >>>> * BPF line info needs ctx->offset[i] to be the offset of >>>> * instruction[i] in jited image, so build prologue first. >>>> */ >>>> - if (build_prologue(&ctx, was_classic)) { >>>> + if (build_prologue(&ctx, was_classic, prog->aux->exception_cb)) { >>>> prog = orig_prog; >>>> goto out_off; >>>> } >>>> @@ -1586,7 +1611,7 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog) >>>> } >>>> >>>> ctx.epilogue_offset = ctx.idx; >>>> - build_epilogue(&ctx); >>>> + build_epilogue(&ctx, prog->aux->exception_cb); >>>> build_plt(&ctx); >>>> >>>> extable_align = __alignof__(struct exception_table_entry); >>>> @@ -1614,7 +1639,7 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog) >>>> ctx.idx = 0; >>>> ctx.exentry_idx = 0; >>>> >>>> - build_prologue(&ctx, was_classic); >>>> + build_prologue(&ctx, was_classic, prog->aux->exception_cb); >>>> >>>> if (build_body(&ctx, extra_pass)) { >>>> bpf_jit_binary_free(header); >>>> @@ -1622,7 +1647,7 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog) >>>> goto out_off; >>>> } >>>> >>>> - build_epilogue(&ctx); >>>> + build_epilogue(&ctx, prog->aux->exception_cb); >>>> build_plt(&ctx); >>>> >>>> /* 3. Extra pass to validate JITed code. */ >>>> @@ -2286,3 +2311,38 @@ int bpf_arch_text_poke(void *ip, enum bpf_text_poke_type poke_type, >>>> >>>> return ret; >>>> } >>>> + >>>> +bool bpf_jit_supports_exceptions(void) >>>> +{ >>>> + /* We unwind through both kernel frames (starting from within bpf_throw call) and >>>> + * BPF frames. Therefore we require FP unwinder to be enabled to walk kernel frames and >>>> + * reach BPF frames in the stack trace. >>>> + * ARM64 kernel is aways compiled with CONFIG_FRAME_POINTER=y >>>> + */ >>>> + return true; >>>> +} >>>> + >>>> +void arch_bpf_stack_walk(bool (*consume_fn)(void *cookie, u64 ip, u64 sp, u64 bp), void *cookie) >>>> +{ >>>> + struct stack_info stacks[] = { >>>> + stackinfo_get_task(current), >>>> + }; >>>> + >>> >>> Seems there is no need to define "stacks" as an array >> >> Sure, will change in next version. >> >>> >>>> + struct unwind_state state = { >>>> + .stacks = stacks, >>>> + .nr_stacks = ARRAY_SIZE(stacks), >>>> + }; >>>> + unwind_init_common(&state, current); >>>> + state.fp = (unsigned long)__builtin_frame_address(1); >>>> + state.pc = (unsigned long)__builtin_return_address(0); >>>> + >>>> + if (unwind_next_frame_record(&state)) >>>> + return; >>>> + while (1) { >>>> + /* We only use the fp in the exception callback. Pass 0 for sp as it's unavailable*/ >>>> + if (!consume_fn(cookie, (u64)state.pc, 0, (u64)state.fp)) >>>> + break; >>>> + if (unwind_next_frame_record(&state)) >>> >>> When PTR_AUTH is implemented, lr is encoded before being pushed to >>> the stack, but unwind_next_frame_record() does not decode state.pc >>> when fetching it from the stack. >> >> Thanks for pointing this out. I will fix this in the next version. >> >>>> + break; >>>> + } >>> >>> And it's better to simplify the if-while(1)-if to: >>> >>> while (!unwind_next_frame_record(&state)) { >>> ... >>> } >> >> Sure, >> Will use this method in the next version. >> >>> >>>> +} >>>> diff --git a/tools/testing/selftests/bpf/DENYLIST.aarch64 b/tools/testing/selftests/bpf/DENYLIST.aarch64 >>>> index f5065576cae9..7f768d335698 100644 >>>> --- a/tools/testing/selftests/bpf/DENYLIST.aarch64 >>>> +++ b/tools/testing/selftests/bpf/DENYLIST.aarch64 >>>> @@ -1,6 +1,5 @@ >>>> bpf_cookie/multi_kprobe_attach_api # kprobe_multi_link_api_subtest:FAIL:fentry_raw_skel_load unexpected error: -3 >>>> bpf_cookie/multi_kprobe_link_api # kprobe_multi_link_api_subtest:FAIL:fentry_raw_skel_load unexpected error: -3 >>>> -exceptions # JIT does not support calling kfunc bpf_throw: -524 >>>> fexit_sleep # The test never returns. The remaining tests cannot start. >>>> kprobe_multi_bench_attach # bpf_program__attach_kprobe_multi_opts unexpected error: -95 >>>> kprobe_multi_test/attach_api_addrs # bpf_program__attach_kprobe_multi_opts unexpected error: -95 >> >> >> Thanks, >> Puranjay