Skip to content

Commit

Permalink
cg: correct bitfield offset determination
Browse files Browse the repository at this point in the history
The C compiler (together with binutils) can represent bitfields using
one of two representations: store the actual offset of the bitfield in
ctm_offset, or store the base offset of the underlying type in ctm_offset
and store the offset within the underlying type as cte_offset.

Signed-off-by: Kris Van Hees <kris.van.hees@oracle.com>
Reviewed-by: Eugene Loh <eugene.loh@oracle.com>
  • Loading branch information
kvanhees committed Aug 3, 2023
1 parent 33eef3a commit 5cbd3a4
Show file tree
Hide file tree
Showing 2 changed files with 139 additions and 44 deletions.
150 changes: 106 additions & 44 deletions libdtrace/dt_cg.c
Original file line number Diff line number Diff line change
Expand Up @@ -2854,61 +2854,122 @@ dt_cg_ptrsize(dt_node_t *dnp, dt_irlist_t *dlp, dt_regset_t *drp,

/*
* If the result of a "." or "->" operation is a bit-field, we use this routine
* to generate an epilogue to the load instruction that extracts the value.
* to generate the load instruction and bit shifts that extracts the value.
*/
static void
dt_cg_field_get(dt_node_t *dnp, dt_irlist_t *dlp, dt_regset_t *drp,
ctf_file_t *fp, const ctf_membinfo_t *mp)
ctf_file_t *fp, const ctf_membinfo_t *mp)
{
ctf_encoding_t e;
uint64_t shift;
int r1;
ctf_encoding_t e;
uint64_t shift;
ssize_t size;
size_t offset;
uint_t op;
int reg = dnp->dn_left->dn_reg;

if (ctf_type_encoding(fp, mp->ctm_type, &e) != 0 || e.cte_bits > 64) {
xyerror(D_UNKNOWN, "cg: bad field: off %lu type <%ld> "
"bits %u\n", mp->ctm_offset, mp->ctm_type, e.cte_bits);
xyerror(D_UNKNOWN, "cg: bad field: member off %lu type <%ld> "
"oofset %u bits %u\n", mp->ctm_offset, mp->ctm_type,
e.cte_offset, e.cte_bits);
}

assert(dnp->dn_op == DT_TOK_PTR || dnp->dn_op == DT_TOK_DOT);
r1 = dnp->dn_left->dn_reg;

offset = mp->ctm_offset + e.cte_offset;

/* Advance to the byte where the bitfield starts (if needed). */
if (offset >= NBBY) {
emit(dlp, BPF_ALU64_IMM(BPF_ADD, reg, offset / NBBY));
offset %= NBBY;
}

/* If this is a REF, we are done. */
if (dnp->dn_flags & DT_NF_REF)
return;

op = dt_cg_ldsize(dnp, fp, mp->ctm_type, &size);
if (dnp->dn_left->dn_flags & (DT_NF_ALLOCA | DT_NF_DPTR))
emit(dlp, BPF_LOAD(op, reg, reg, 0));
else
dt_cg_load_scalar(dnp->dn_left, op, size, dlp, drp);

#if 0
/*
* On little-endian architectures, ctm_offset counts from the right so
* ctm_offset % NBBY itself is the amount we want to shift right to
* move the value bits to the little end of the register to mask them.
* On big-endian architectures, ctm_offset counts from the left so we
* must subtract (ctm_offset % NBBY + cte_bits) from the size in bits
* we used for the load. The size of our load in turn is found by
* rounding cte_bits up to a byte boundary and then finding the
* nearest power of two to this value (see clp2(), above).
* On little-endian architectures, offset counts from the right so
* offset itself is the amount we want to shift right to move the value
* bits to the little end of the register to mask them.
* On big-endian architectures, offset counts from the left so we must
* subtract (offset + cte_bits) from the size in bits we used for the
* load. The size of our load in turn is found by rounding cte_bits up
* to a byte boundary and then finding the nearest power of two to this
* value (see clp2(), above).
*
* For signed values, we first shift left to ensure that the leftmost
* bit set the sign, so that a subsequent sign-extending right shift
* ensure we get the correct signed value.
*/
if (dnp->dn_flags & DT_NF_SIGNED) {
/*
* r1 <<= 64 - shift
* r1 >>= 64 - bits
* reg <<= 64 - shift
* reg >>= 64 - bits
*/
#ifdef _BIG_ENDIAN
shift = clp2(P2ROUNDUP(e.cte_bits, NBBY) / NBBY) * NBBY -
mp->ctm_offset % NBBY;
offset;
#else
shift = mp->ctm_offset % NBBY + e.cte_bits;
shift = offset + e.cte_bits;
#endif
emit(dlp, BPF_ALU64_IMM(BPF_LSH, r1, 64 - shift));
emit(dlp, BPF_ALU64_IMM(BPF_ARSH, r1, 64 - e.cte_bits));
if (shift < 64)
emit(dlp, BPF_ALU64_IMM(BPF_LSH, reg, 64 - shift));
emit(dlp, BPF_ALU64_IMM(BPF_ARSH, reg, 64 - e.cte_bits));
} else {
/*
* r1 >>= shift
* r1 &= (1 << bits) - 1
* reg >>= shift
* reg &= (1 << bits) - 1
*/
#ifdef _BIG_ENDIAN
shift = clp2(P2ROUNDUP(e.cte_bits, NBBY) / NBBY) * NBBY -
(mp->ctm_offset % NBBY + e.cte_bits);
(offset + e.cte_bits);
#else
shift = mp->ctm_offset % NBBY;
shift = offset;
#endif
emit(dlp, BPF_ALU64_IMM(BPF_RSH, r1, shift));
emit(dlp, BPF_ALU64_IMM(BPF_AND, r1, (1ULL << e.cte_bits) - 1));
if (shift)
emit(dlp, BPF_ALU64_IMM(BPF_RSH, reg, shift));
emit(dlp, BPF_ALU64_IMM(BPF_AND, reg, (1ULL << e.cte_bits) - 1));
}
#else
/*
* Shift the loaded value left (64 - cte_bits - shift) bits to mask off
* higher order bits, and then shift right (64 - cte_bits) bits (with
* possible sign extending) to move the bitfield value to the low end
* of the register.
*
* On little-endian architectures, offset counts from the right so the
* shift we want is the offset itself.
*
* On big-endian architectures, offset counts from the left so we must
* subtract (offset + cte_bits) from the load size (in bits) to
* calculate the left shift amount. The size of the load is found by
* rounding cte_bits up to a byte boundary and then determining the
* nearest power of two to this value (see clp2(), above).
*/
#ifdef _BIG_ENDIAN
shift = clp2(P2ROUNDUP(e.cte_bits, NBBY) / NBBY) * NBBY -
(offset + e.cte_bits);
#else
shift = offset;
#endif

/*
* reg <<= 64 - bits - shift
* reg >>= 64 - bits (sign-extending if signed)
*/
emit(dlp, BPF_ALU64_IMM(BPF_LSH, reg, 64 - e.cte_bits - shift));
if (dnp->dn_flags & DT_NF_SIGNED)
emit(dlp, BPF_ALU64_IMM(BPF_ARSH, reg, 64 - e.cte_bits));
else
emit(dlp, BPF_ALU64_IMM(BPF_RSH, reg, 64 - e.cte_bits));
#endif
}

/*
Expand All @@ -2926,6 +2987,7 @@ dt_cg_field_set(dt_node_t *src, dt_irlist_t *dlp,
{
uint64_t cmask, fmask, shift;
int r1, r2;
size_t offset;

ctf_membinfo_t m;
ctf_encoding_t e;
Expand All @@ -2950,10 +3012,13 @@ dt_cg_field_set(dt_node_t *src, dt_irlist_t *dlp,
}

if (ctf_type_encoding(fp, m.ctm_type, &e) != 0 || e.cte_bits > 64) {
xyerror(D_UNKNOWN, "cg: bad field: off %lu type <%ld> "
"bits %u\n", m.ctm_offset, m.ctm_type, e.cte_bits);
xyerror(D_UNKNOWN, "cg: bad field: member off %lu type <%ld> "
"offset %u bits %u\n", m.ctm_offset, m.ctm_type,
e.cte_offset, e.cte_bits);
}

offset = m.ctm_offset + e.cte_offset;

if ((r1 = dt_regset_alloc(drp)) == -1 ||
(r2 = dt_regset_alloc(drp)) == -1)
longjmp(yypcb->pcb_jmpbuf, EDT_NOREG);
Expand All @@ -2968,9 +3033,9 @@ dt_cg_field_set(dt_node_t *src, dt_irlist_t *dlp,
*/
#ifdef _BIG_ENDIAN
shift = clp2(P2ROUNDUP(e.cte_bits, NBBY) / NBBY) * NBBY -
(m.ctm_offset % NBBY + e.cte_bits);
(offset % NBBY + e.cte_bits);
#else
shift = m.ctm_offset % NBBY;
shift = offset % NBBY;
#endif
fmask = (1ULL << e.cte_bits) - 1;
cmask = ~(fmask << shift);
Expand All @@ -2989,7 +3054,8 @@ dt_cg_field_set(dt_node_t *src, dt_irlist_t *dlp,
emit(dlp, BPF_ALU64_REG(BPF_AND, r1, r2));
dt_cg_setx(dlp, r2, fmask);
emit(dlp, BPF_ALU64_REG(BPF_AND, r2, src->dn_reg));
emit(dlp, BPF_ALU64_IMM(BPF_LSH, r2, shift));
if (shift > 0)
emit(dlp, BPF_ALU64_IMM(BPF_LSH, r2, shift));
emit(dlp, BPF_ALU64_REG(BPF_OR, r1, r2));
dt_regset_free(drp, r2);

Expand Down Expand Up @@ -6389,6 +6455,7 @@ dt_cg_node(dt_node_t *dnp, dt_irlist_t *dlp, dt_regset_t *drp)
}

dt_cg_check_notnull(dlp, drp, dnp->dn_left->dn_reg);
dnp->dn_reg = dnp->dn_left->dn_reg;

ctfp = dnp->dn_left->dn_ctfp;
type = ctf_type_resolve(ctfp, dnp->dn_left->dn_type);
Expand All @@ -6404,15 +6471,14 @@ dt_cg_node(dt_node_t *dnp, dt_irlist_t *dlp, dt_regset_t *drp)
longjmp(yypcb->pcb_jmpbuf, EDT_CTF);
}

if (m.ctm_offset != 0) {
/*
* If the offset is not aligned on a byte boundary, it
* is a bit-field member and we will extract the value
* bits below after we generate the appropriate load.
*/
emit(dlp, BPF_ALU64_IMM(BPF_ADD, dnp->dn_left->dn_reg, m.ctm_offset / NBBY));
if (dnp->dn_flags & DT_NF_BITFIELD) {
dt_cg_field_get(dnp, dlp, drp, ctfp, &m);
break;
}

if (m.ctm_offset != 0)
emit(dlp, BPF_ALU64_IMM(BPF_ADD, dnp->dn_left->dn_reg, m.ctm_offset / NBBY));

if (!(dnp->dn_flags & DT_NF_REF)) {
uint_t op;
ssize_t size;
Expand All @@ -6423,12 +6489,8 @@ dt_cg_node(dt_node_t *dnp, dt_irlist_t *dlp, dt_regset_t *drp)
emit(dlp, BPF_LOAD(op, dnp->dn_left->dn_reg, dnp->dn_left->dn_reg, 0));
else
dt_cg_load_scalar(dnp->dn_left, op, size, dlp, drp);

if (dnp->dn_flags & DT_NF_BITFIELD)
dt_cg_field_get(dnp, dlp, drp, ctfp, &m);
}

dnp->dn_reg = dnp->dn_left->dn_reg;
break;
}

Expand Down
33 changes: 33 additions & 0 deletions test/unittest/bitfields/tst.bitfield-offset.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Oracle Linux DTrace.
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* Licensed under the Universal Permissive License v 1.0 as shown at
* http://oss.oracle.com/licenses/upl.
*/

/*
* Verify that DTrace is correctly determining the offset of a bitfield. It
* used to only look at ctm_offset to know the location of the bitfield in the
* parent type, but some compiler/libctf combinations use ctm_offset for the
* offset of the underlying type and depend on cte_offset instead to provide
* the offset within that underlying type.
*/
#pragma D option quiet

struct iphdr iph;

BEGIN
{
iph.ihl = 5;
iph.version = 4;

trace(iph.ihl);
trace(iph.version);

exit(iph.ihl == 5 && iph.version == 4 ? 0 : 1);
}

ERROR
{
exit(1);
}

0 comments on commit 5cbd3a4

Please sign in to comment.