[DTrace-devel] [PATCH v2 03/17] cg: fault improvements
Eugene Loh
eugene.loh at oracle.com
Thu Mar 24 21:50:37 UTC 2022
On 3/24/22 4:15 PM, Nick Alcock wrote:
> On 24 Mar 2022, Eugene Loh via DTrace-devel verbalised:
>
>> On 3/14/22 5:30 PM, Nick Alcock wrote:
>>
>>> Add a label to dt_cg_probe_error, which is dropped on the first
>> Dropped? That sounds like "omitted". How about "attached to"?
> Much better! Adjusted.
>
>> But what is even the point of this? Most of the time, the label is not needed. For the few times you want it, just insert
>> emitl(dlp, MYLABEL,
>> BPF_NOP());
>> in front of the dt_cg_probe_error() call. The NOP will be optimized away anyhow and you'll get the same BPF instructions.
> Errr... the NOP ends up being an actual instruction in the stream.
I don't think so. Not if there is a nontrivial label. I think the
magic is due to dt_irlist_append(), which has:
if (dip->di_label == DT_LBL_NONE || !BPF_IS_NOP(dip->di_instr))
dlp->dl_len++;
With a non-NONE label and a NOP instruction, we don't include the
instruction. I just tried it and sure enough the NOP does not appear
(unless I specify DT_LBL_NONE).
> I
> thought we were at least trying to minimize our insn count, at least for
> frequently-used things like the bounds checkers, where the complexity
> cost of doing so is low?
>
>>> instruction if set; eschew regset allocation on the grounds that
>> If set? According to this patch, the label is always set. I'd omit
>> "if set". (Though mostly, as I said, I'd omit the label stuff
>> altogether.)
> DT_LBL_NONE is "unset", isn't it?
I guess. But to convince myself of that I have to look beyond the
patch. Oh well. Should be moot at this point?
>
>> I suppose "eschew" is fine, but "do not use" is simpler.
> True! Adjusted.
>
>>> spilling to the stack is pointless when the regs will all be thrown away
>>> in any case, and can trigger errors if some of the regs are not properly
>>> initialized, as is relatively likely in an error condition.
>> Good point. Indeed, I think you can be more emphatic:
>> s/can trigger errors/will trigger BPF verifier errors/
>> s/ properly//
> Adjusted.
>
>>> Also add a new dt_cg_probe_error_regval, which is like dt_cg_probe_error
>>> except the last argument is not an illegal value but a register whose
>>> value should be emitted.
>> Actually, I like what you did with dt_cg_trace(). How about just
>> adding an isreg flag to dt_cg_probe_error() since the differences
>> between the existing and new functions are otherwise minimal.
> Back when I wrote this dt_cg_probe_error had so many args I couldn't
> remember which was which as it was. It has fewer now, thanks to Kris,
> and this actually lets us simplify a lot of uses of regval, like this:
>
> if (sizemax < 0)
> dt_cg_probe_error(yypcb, lbl_size_err, DTRACEFLT_BADSIZE, size);
> else
> dt_cg_probe_error_regval(yypcb, lbl_size_err, DTRACEFLT_BADSIZE,
> size);
>
> which can now become a simple
>
> dt_cg_probe_error(yypcb, lbl_size_erro, DTRACEFLT_BADSIZE,
> (sizemax >= 0), size);
>
> ("if sizemax >= 0, this is a reg").
>
> Adjusted!
>
>>> This is a bit trickier: we must move the
>>> illegal register arg into place first because we don't use the regset
>>> allocation code, so it might be using one of the regs we're about to
>>> overwrite; and don't emit a move at all if we're by chance already using
>>> that reg (the verifier doesn't like mov %r4, %r4 very much).
>> I'd omit the stuff from "This is a bit tricker" until the end of the
>> paragraph. It seems premature to get into that detail here. The
>> comments in the code suffice.
> (Done.)
>
>> Incidentally, are you sure "mov %r4 %r4" is problematic? If %r4 is
>> readable, this should be just fine. And someday I can imagine we'll
>> do code optimization that removes such no-ops.
> I wish, but nope! It's broken for me, at least with some kernels (5.15
> was the one I saw it on). It might be specific to the
> using-non-allocated-register thing though, which is pretty specific to
> faulting paths. (Though I didn't think allocating registers did anything
> visible at the BPF layer, so probably not.)
I can well believe %reg=%reg would be considered illegal if %reg is not
"readable". E.g. just got back from a function call and %r1-%r5 were
clobbered.
>> Also, the issue is not that regset_alloc() is not being used. Even if
>> you used regset_alloc_args(), you'd still want to write %r4 first.
> True enough!
>
>>> @@ -666,16 +669,58 @@ dt_cg_probe_error(dt_pcb_t *pcb, uint32_t off, uint32_t fault, uint64_t illval)
>>> * // call dt_probe_error
>>> * return; // exit
>>> */
>> Incidentally, the comment block (not completely visible here) has DT_STK_DCT. That's a pre-existing problem but might as well clean
>> up that typo while you're in here.
> (Fixed.)
>
>>> - if (dt_regset_xalloc_args(drp) == -1)
>>> - longjmp(yypcb->pcb_jmpbuf, EDT_NOREG);
>>> - emit(dlp, BPF_LOAD(BPF_DW, BPF_REG_1, BPF_REG_FP, DT_STK_DCTX));
>>> +
>>> + emitl(dlp, lbl,
>>> + BPF_LOAD(BPF_DW, BPF_REG_1, BPF_REG_FP, DT_STK_DCTX));
>>> emit(dlp, BPF_MOV_IMM(BPF_REG_2, off));
>>> emit(dlp, BPF_MOV_IMM(BPF_REG_3, fault));
>>> emit(dlp, BPF_MOV_IMM(BPF_REG_4, illval));
>>> - dt_regset_xalloc(drp, BPF_REG_0);
>>> emite(dlp, BPF_CALL_FUNC(idp->di_id), idp);
>>> - dt_regset_free_args(drp);
>>> - dt_regset_free(drp, BPF_REG_0);
>>> + emit(dlp, BPF_RETURN());
>>> +}
>>> +
>>> +/*
>>> + * Generate code for a fault condition with the illval coming from a register.
>>> + * Drop a label at the start.
>> Again, I advise against this label stuff, but regardless "drop" sounds like "omit".
> Yep. Changed to "... possibly at a given label". Keeping the label stuff
> simply because it saves several instructions per bounds check and the
> bounds checks are too damn big already and can't be moved out of line :(
How does it save instructions? Is this back to the question of whether
those NOPs appear in the instruction stream?
>>> + * Doesn't use the regset code on the grounds that when this is executed we will
>>> + * only reuse one caller's reg in a stereotyped manner.
>> Well, or that we are about to exit the function and so there is no point in spilling register contents to the stack.
> Changed to
>
> * Doesn't use the regset code on the grounds that when this is executed we will
> * never reuse any of the callers' regs in any case.
>
> (some time ago.)
>
>>> + */
>>> +static void
>>> +dt_cg_probe_error_regval(dt_pcb_t *pcb, int lbl, uint32_t off,
>>> + uint32_t fault, int illreg)
>>> +{
>>> + dt_irlist_t *dlp = &pcb->pcb_ir;
>>> + dt_ident_t *idp = dt_dlib_get_func(yypcb->pcb_hdl,
>>> + "dt_probe_error");
>>> +
>>> + assert(idp != NULL);
>>> +
>>> + /*
>>> + * dt_probe_error(
>>> + * dctx, // lddw %r1, %fp, DT_STK_DCTX
>>> + * off, // mov %r2, off
>>> + * fault, // mov %r3, fault
>>> + * illreg); // mov %r4, %illreg
>> I'm not a fan of these comments with disassembly lines but in any case here the disassembly instructions in the comment are in a
>> different order from what one actually does.
> Yeah, I've stopped using them too now I can read this stuff better. This
> whole function is gone now in favour of an ILLISREG arg to
> dt_cg_probe_error anyway. :)
>
More information about the DTrace-devel
mailing list