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  }