github.com/looshlee/beatles@v0.0.0-20220727174639-742810ab631c/pkg/alignchecker/alignchecker.go (about) 1 // Copyright 2018-2019 Authors of Cilium 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package alignchecker 16 17 import ( 18 "debug/dwarf" 19 "debug/elf" 20 "fmt" 21 "reflect" 22 ) 23 24 // CheckStructAlignments checks whether size and offsets match of the given 25 // C and Go structs which are listed in the given toCheck map (C struct name => 26 // Go struct []reflect.Type). 27 // 28 // C struct size info is extracted from the given ELF object file debug section 29 // encoded in DWARF. 30 // 31 // To find a matching C struct field, a Go field has to be tagged with 32 // `align:"field_name_in_c_struct". In the case of unnamed union field, such 33 // union fields can be referred with special tags - `align:"$union0"`, 34 // `align:"$union1"`, etc. 35 func CheckStructAlignments(pathToObj string, toCheck map[string][]reflect.Type) error { 36 f, err := elf.Open(pathToObj) 37 if err != nil { 38 return fmt.Errorf("elf failed to open %s: %s", pathToObj, err) 39 } 40 defer f.Close() 41 42 d, err := getDWARFFromELF(f) 43 if err != nil { 44 return fmt.Errorf("cannot parse DWARF debug info %s: %s", pathToObj, err) 45 } 46 47 structInfo, err := getStructInfosFromDWARF(d, toCheck) 48 if err != nil { 49 return fmt.Errorf("cannot extract struct info from DWARF %s: %s", pathToObj, err) 50 } 51 52 for cName, goStructs := range toCheck { 53 if err := check(cName, goStructs, structInfo); err != nil { 54 return err 55 } 56 } 57 return nil 58 } 59 60 // structInfo contains C struct info 61 type structInfo struct { 62 size int64 63 fieldOffsets map[string]int64 64 } 65 66 func getStructInfosFromDWARF(d *dwarf.Data, toCheck map[string][]reflect.Type) (map[string]structInfo, error) { 67 structs := make(map[string]structInfo) 68 69 r := d.Reader() 70 71 for entry, err := r.Next(); entry != nil && err == nil; entry, err = r.Next() { 72 // Read only DWARF struct entries 73 if entry.Tag != dwarf.TagStructType { 74 continue 75 } 76 77 t, err := d.Type(entry.Offset) 78 if err != nil { 79 return nil, fmt.Errorf("cannot read DWARF info section at offset %d: %s", 80 entry.Offset, err) 81 } 82 83 st := t.(*dwarf.StructType) 84 85 if _, found := toCheck[st.StructName]; found { 86 unionCount := 0 87 offsets := make(map[string]int64) 88 for _, field := range st.Field { 89 n := field.Name 90 // Create surrogate names ($union0, $union1, etc) for unnamed 91 // union members 92 if n == "" { 93 if t, ok := field.Type.(*dwarf.StructType); ok { 94 if t.Kind == "union" { 95 n = fmt.Sprintf("$union%d", unionCount) 96 unionCount++ 97 } 98 } 99 } 100 offsets[n] = field.ByteOffset 101 } 102 structs[st.StructName] = structInfo{ 103 size: st.ByteSize, 104 fieldOffsets: offsets, 105 } 106 } 107 } 108 109 return structs, nil 110 } 111 112 func check(name string, toCheck []reflect.Type, structs map[string]structInfo) error { 113 for _, g := range toCheck { 114 c, found := structs[name] 115 if !found { 116 return fmt.Errorf("could not find C struct %s", name) 117 } 118 119 if c.size != int64(g.Size()) { 120 return fmt.Errorf("%s(%d) size does not match %s(%d)", g, g.Size(), 121 name, c.size) 122 } 123 124 for i := 0; i < g.NumField(); i++ { 125 fieldName := g.Field(i).Tag.Get("align") 126 // Ignore fields without `align` struct tag 127 if fieldName == "" { 128 continue 129 } 130 goOffset := int64(g.Field(i).Offset) 131 cOffset := structs[name].fieldOffsets[fieldName] 132 if goOffset != cOffset { 133 return fmt.Errorf("%s.%s offset(%d) does not match %s.%s(%d)", 134 g, g.Field(i).Name, goOffset, name, fieldName, cOffset) 135 } 136 } 137 } 138 139 return nil 140 }