// Basic types
// A BasicType holds fields common to all basic types.
+//
+// See the documentation for StructField for more info on the interpretation of
+// the BitSize/BitOffset/DataBitOffset fields.
type BasicType struct {
CommonType
- BitSize int64
- BitOffset int64
+ BitSize int64
+ BitOffset int64
+ DataBitOffset int64
}
func (b *BasicType) Basic() *BasicType { return b }
}
// A StructField represents a field in a struct, union, or C++ class type.
+//
+// Bit Fields
+//
+// The BitSize, BitOffset, and DataBitOffset fields describe the bit
+// size and offset of data members declared as bit fields in C/C++
+// struct/union/class types.
+//
+// BitSize is the number of bits in the bit field.
+//
+// DataBitOffset, if non-zero, is the number of bits from the start of
+// the enclosing entity (e.g. containing struct/class/union) to the
+// start of the bit field. This corresponds to the DW_AT_data_bit_offset
+// DWARF attribute that was introduced in DWARF 4.
+//
+// BitOffset, if non-zero, is the number of bits between the most
+// significant bit of the storage unit holding the bit field to the
+// most significant bit of the bit field. Here "storage unit" is the
+// type name before the bit field (for a field "unsigned x:17", the
+// storage unit is "unsigned"). BitOffset values can vary depending on
+// the endianness of the system. BitOffset corresponds to the
+// DW_AT_bit_offset DWARF attribute that was deprecated in DWARF 4 and
+// removed in DWARF 5.
+//
+// At most one of DataBitOffset and BitOffset will be non-zero;
+// DataBitOffset/BitOffset will only be non-zero if BitSize is
+// non-zero. Whether a C compiler uses one or the other
+// will depend on compiler vintage and command line options.
+//
+// Here is an example of C/C++ bit field use, along with what to
+// expect in terms of DWARF bit offset info. Consider this code:
+//
+// struct S {
+// int q;
+// int j:5;
+// int k:6;
+// int m:5;
+// int n:8;
+// } s;
+//
+// For the code above, one would expect to see the following for
+// DW_AT_bit_offset values (using GCC 8):
+//
+// Little | Big
+// Endian | Endian
+// |
+// "j": 27 | 0
+// "k": 21 | 5
+// "m": 16 | 11
+// "n": 8 | 16
+//
+// Note that in the above the offsets are purely with respect to the
+// containing storage unit for j/k/m/n -- these values won't vary based
+// on the size of prior data members in the containing struct.
+//
+// If the compiler emits DW_AT_data_bit_offset, the expected values
+// would be:
+//
+// "j": 32
+// "k": 37
+// "m": 43
+// "n": 48
+//
+// Here the value 32 for "j" reflects the fact that the bit field is
+// preceded by other data members (recall that DW_AT_data_bit_offset
+// values are relative to the start of the containing struct). Hence
+// DW_AT_data_bit_offset values can be quite large for structs with
+// many fields.
+//
+// DWARF also allow for the possibility of base types that have
+// non-zero bit size and bit offset, so this information is also
+// captured for base types, but it is worth noting that it is not
+// possible to trigger this behavior using mainstream languages.
+//
type StructField struct {
- Name string
- Type Type
- ByteOffset int64
- ByteSize int64 // usually zero; use Type.Size() for normal fields
- BitOffset int64 // within the ByteSize bytes at ByteOffset
- BitSize int64 // zero if not a bit field
+ Name string
+ Type Type
+ ByteOffset int64
+ ByteSize int64 // usually zero; use Type.Size() for normal fields
+ BitOffset int64
+ DataBitOffset int64
+ BitSize int64 // zero if not a bit field
}
func (t *StructType) String() string {
return t.Defn()
}
+func (f *StructField) bitOffset() int64 {
+ if f.BitOffset != 0 {
+ return f.BitOffset
+ }
+ return f.DataBitOffset
+}
+
func (t *StructType) Defn() string {
s := t.Kind
if t.StructName != "" {
s += "@" + strconv.FormatInt(f.ByteOffset, 10)
if f.BitSize > 0 {
s += " : " + strconv.FormatInt(f.BitSize, 10)
- s += "@" + strconv.FormatInt(f.BitOffset, 10)
+ s += "@" + strconv.FormatInt(f.bitOffset(), 10)
}
}
s += "}"
// AttrName: name of base type in programming language of the compilation unit [required]
// AttrEncoding: encoding value for type (encFloat etc) [required]
// AttrByteSize: size of type in bytes [required]
- // AttrBitOffset: for sub-byte types, size in bits
- // AttrBitSize: for sub-byte types, bit offset of high order bit in the AttrByteSize bytes
+ // AttrBitOffset: bit offset of value within containing storage unit
+ // AttrDataBitOffset: bit offset of value within containing storage unit
+ // AttrBitSize: size in bits
+ //
+ // For most languages BitOffset/DataBitOffset/BitSize will not be present
+ // for base types.
name, _ := e.Val(AttrName).(string)
enc, ok := e.Val(AttrEncoding).(int64)
if !ok {
t.Name = name
t.BitSize, _ = e.Val(AttrBitSize).(int64)
haveBitOffset := false
- if t.BitOffset, haveBitOffset = e.Val(AttrBitOffset).(int64); !haveBitOffset {
- t.BitOffset, _ = e.Val(AttrDataBitOffset).(int64)
+ haveDataBitOffset := false
+ t.BitOffset, haveBitOffset = e.Val(AttrBitOffset).(int64)
+ t.DataBitOffset, haveDataBitOffset = e.Val(AttrDataBitOffset).(int64)
+ if haveBitOffset && haveDataBitOffset {
+ err = DecodeError{name, e.Offset, "duplicate bit offset attributes"}
+ goto Error
}
case TagClassType, TagStructType, TagUnionType:
// AttrType: type of member [required]
// AttrByteSize: size in bytes
// AttrBitOffset: bit offset within bytes for bit fields
+ // AttrDataBitOffset: field bit offset relative to struct start
// AttrBitSize: bit size for bit fields
// AttrDataMemberLoc: location within struct [required for struct, class]
// There is much more to handle C++, all ignored for now.
t.Incomplete = e.Val(AttrDeclaration) != nil
t.Field = make([]*StructField, 0, 8)
var lastFieldType *Type
- var lastFieldBitOffset int64
+ var lastFieldBitSize int64
+ var lastFieldByteOffset int64
for kid := next(); kid != nil; kid = next() {
if kid.Tag != TagMember {
continue
f.ByteOffset = loc
}
- haveBitOffset := false
f.Name, _ = kid.Val(AttrName).(string)
f.ByteSize, _ = kid.Val(AttrByteSize).(int64)
- if f.BitOffset, haveBitOffset = kid.Val(AttrBitOffset).(int64); !haveBitOffset {
- f.BitOffset, haveBitOffset = kid.Val(AttrDataBitOffset).(int64)
+ haveBitOffset := false
+ haveDataBitOffset := false
+ f.BitOffset, haveBitOffset = kid.Val(AttrBitOffset).(int64)
+ f.DataBitOffset, haveDataBitOffset = kid.Val(AttrDataBitOffset).(int64)
+ if haveBitOffset && haveDataBitOffset {
+ err = DecodeError{name, e.Offset, "duplicate bit offset attributes"}
+ goto Error
}
f.BitSize, _ = kid.Val(AttrBitSize).(int64)
t.Field = append(t.Field, f)
- bito := f.BitOffset
- if !haveBitOffset {
- bito = f.ByteOffset * 8
- }
- if bito == lastFieldBitOffset && t.Kind != "union" {
+ if lastFieldBitSize == 0 && lastFieldByteOffset == f.ByteOffset && t.Kind != "union" {
// Last field was zero width. Fix array length.
// (DWARF writes out 0-length arrays as if they were 1-length arrays.)
fixups.recordArrayType(lastFieldType)
}
lastFieldType = &f.Type
- lastFieldBitOffset = bito
+ lastFieldByteOffset = f.ByteOffset
+ lastFieldBitSize = f.BitSize
}
if t.Kind != "union" {
b, ok := e.Val(AttrByteSize).(int64)
- if ok && b*8 == lastFieldBitOffset {
+ if ok && b == lastFieldByteOffset {
// Final field must be zero width. Fix array length.
fixups.recordArrayType(lastFieldType)
}
return d
}
-func TestTypedefsELF(t *testing.T) { testTypedefs(t, elfData(t, "testdata/typedef.elf"), "elf") }
+func TestTypedefsELF(t *testing.T) {
+ testTypedefs(t, elfData(t, "testdata/typedef.elf"), "elf", typedefTests)
+}
func TestTypedefsMachO(t *testing.T) {
- testTypedefs(t, machoData(t, "testdata/typedef.macho"), "macho")
+ testTypedefs(t, machoData(t, "testdata/typedef.macho"), "macho", typedefTests)
}
-func TestTypedefsELFDwarf4(t *testing.T) { testTypedefs(t, elfData(t, "testdata/typedef.elf4"), "elf") }
+func TestTypedefsELFDwarf4(t *testing.T) {
+ testTypedefs(t, elfData(t, "testdata/typedef.elf4"), "elf", typedefTests)
+}
-func testTypedefs(t *testing.T, d *Data, kind string) {
+func testTypedefs(t *testing.T, d *Data, kind string, testcases map[string]string) {
r := d.Reader()
seen := make(map[string]bool)
for {
typstr = t1.Type.String()
}
- if want, ok := typedefTests[t1.Name]; ok {
+ if want, ok := testcases[t1.Name]; ok {
if seen[t1.Name] {
t.Errorf("multiple definitions for %s", t1.Name)
}
}
}
- for k := range typedefTests {
+ for k := range testcases {
if !seen[k] {
t.Errorf("missing %s", k)
}
}
}
-func TestBitOffsetsELF(t *testing.T) { testBitOffsets(t, elfData(t, "testdata/typedef.elf")) }
+var expectedBitOffsets1 = map[string]string{
+ "x": "S:1 DBO:32",
+ "y": "S:4 DBO:33",
+}
+
+var expectedBitOffsets2 = map[string]string{
+ "x": "S:1 BO:7",
+ "y": "S:4 BO:27",
+}
+
+func TestBitOffsetsELF(t *testing.T) {
+ f := "testdata/typedef.elf"
+ testBitOffsets(t, elfData(t, f), f, expectedBitOffsets2)
+}
func TestBitOffsetsMachO(t *testing.T) {
- testBitOffsets(t, machoData(t, "testdata/typedef.macho"))
+ f := "testdata/typedef.macho"
+ testBitOffsets(t, machoData(t, f), f, expectedBitOffsets2)
}
func TestBitOffsetsMachO4(t *testing.T) {
- testBitOffsets(t, machoData(t, "testdata/typedef.macho4"))
+ f := "testdata/typedef.macho4"
+ testBitOffsets(t, machoData(t, f), f, expectedBitOffsets1)
}
func TestBitOffsetsELFDwarf4(t *testing.T) {
- testBitOffsets(t, elfData(t, "testdata/typedef.elf4"))
+ f := "testdata/typedef.elf4"
+ testBitOffsets(t, elfData(t, f), f, expectedBitOffsets1)
+}
+
+func TestBitOffsetsELFDwarf5(t *testing.T) {
+ f := "testdata/typedef.elf5"
+ testBitOffsets(t, elfData(t, f), f, expectedBitOffsets1)
}
-func testBitOffsets(t *testing.T, d *Data) {
+func testBitOffsets(t *testing.T, d *Data, tag string, expectedBitOffsets map[string]string) {
r := d.Reader()
for {
e, err := r.Next()
t1 := typ.(*StructType)
+ bitInfoDump := func(f *StructField) string {
+ res := fmt.Sprintf("S:%d", f.BitSize)
+ if f.BitOffset != 0 {
+ res += fmt.Sprintf(" BO:%d", f.BitOffset)
+ }
+ if f.DataBitOffset != 0 {
+ res += fmt.Sprintf(" DBO:%d", f.DataBitOffset)
+ }
+ return res
+ }
+
for _, field := range t1.Field {
// We're only testing for bitfields
if field.BitSize == 0 {
continue
}
-
- // Ensure BitOffset is not zero
- if field.BitOffset == 0 {
- t.Errorf("bit offset of field %s in %s %s is not set", field.Name, t1.Kind, t1.StructName)
+ got := bitInfoDump(field)
+ want := expectedBitOffsets[field.Name]
+ if got != want {
+ t.Errorf("%s: field %s in %s: got info %q want %q", tag, field.Name, t1.StructName, got, want)
}
}
}
}
}
}
+
+var bitfieldTests = map[string]string{
+ "t_another_struct": "struct another_struct {quix short unsigned int@0; xyz [0]int@4; x unsigned int@4 : 1@31; array [40]long long int@8}",
+}
+
+// TestBitFieldZeroArrayIssue50685 checks to make sure that the DWARF
+// type reading code doesn't get confused by the presence of a
+// specifically-sized bitfield member immediately following a field
+// whose type is a zero-length array. Prior to the fix for issue
+// 50685, we would get this type for the case in testdata/bitfields.c:
+//
+// another_struct {quix short unsigned int@0; xyz [-1]int@4; x unsigned int@4 : 1@31; array [40]long long int@8}
+//
+// Note the "-1" for the xyz field, which should be zero.
+//
+func TestBitFieldZeroArrayIssue50685(t *testing.T) {
+ f := "testdata/bitfields.elf4"
+ testTypedefs(t, elfData(t, f), "elf", bitfieldTests)
+}