github.com/cloudwego/frugal@v0.1.15/internal/loader/loader_amd64_test.go (about)

     1  /*
     2   * Copyright 2022 ByteDance Inc.
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  package loader
    18  
    19  import (
    20      `fmt`
    21      `reflect`
    22      `runtime`
    23      `testing`
    24      `unsafe`
    25  
    26      `github.com/cloudwego/iasm/x86_64`
    27      `github.com/cloudwego/frugal/internal/rt`
    28      `github.com/stretchr/testify/assert`
    29      `github.com/stretchr/testify/require`
    30      `golang.org/x/arch/x86/x86asm`
    31  )
    32  
    33  type funcInfo struct {
    34      *_Func
    35      datap *_ModuleData
    36  }
    37  
    38  func (self funcInfo) entry() uintptr {
    39      if runtime.Version() <= "go1.17" {
    40          return *(*uintptr)(unsafe.Pointer(self._Func))
    41      }
    42      off := uintptr(*(*uint32)(unsafe.Pointer(self._Func)))
    43      off += self.datap.text
    44      return off
    45  }
    46  
    47  //go:linkname findfunc runtime.findfunc
    48  //goland:noinspection GoUnusedParameter
    49  func findfunc(pc uintptr) funcInfo
    50  
    51  //go:linkname pcdatavalue2 runtime.pcdatavalue2
    52  //goland:noinspection GoUnusedParameter
    53  func pcdatavalue2(f funcInfo, table uint32, targetpc uintptr) (int32, uintptr)
    54  
    55  func TestLoader_Load(t *testing.T) {
    56      var src string
    57      var asm x86_64.Assembler
    58      if runtime.Version() < "go1.17" { src += `
    59          movq 8(%rsp), %rax`
    60      }
    61      src += `
    62          movq $1234, (%rax)
    63          ret`
    64      require.NoError(t, asm.Assemble(src))
    65      v0 := 0
    66      cc := asm.Code()
    67      fp := Loader(cc).Load("test", rt.Frame{})
    68      (*(*func(*int))(unsafe.Pointer(&fp)))(&v0)
    69      pc := *(*uintptr)(fp)
    70      assert.Equal(t, 1234, v0)
    71      assert.Equal(t, fmt.Sprintf("(frugal).test_%x", pc), runtime.FuncForPC(pc).Name())
    72      file, line := runtime.FuncForPC(pc).FileLine(pc + 1)
    73      assert.Equal(t, "(jit-generated)", file)
    74      assert.Equal(t, 1, line)
    75      smi, startpc := pcdatavalue2(findfunc(pc), _PCDATA_StackMapIndex, pc + uintptr(len(cc)) - 1)
    76      assert.Equal(t, int32(0), smi)
    77      assert.Equal(t, pc, startpc)
    78      aup, startpc2 := pcdatavalue2(findfunc(pc), _PCDATA_UnsafePoint, pc + uintptr(len(cc)) - 1)
    79      assert.Equal(t, int32(_PCDATA_UnsafePointUnsafe), aup)
    80      assert.Equal(t, pc, startpc2)
    81  }
    82  
    83  func mkpointer() *int {
    84      ret := new(int)
    85      *ret = 1234
    86      runtime.SetFinalizer(ret, func(_ *int) {
    87          println("ret has been recycled")
    88      })
    89      println("ret is allocated")
    90      return ret
    91  }
    92  
    93  func collect() {
    94      println("start collecting")
    95      for i := 1; i < 1000; i++ {
    96          runtime.GC()
    97      }
    98      println("done collecting")
    99  }
   100  
   101  func TestLoader_StackMap(t *testing.T) {
   102      var asm x86_64.Assembler
   103      var smb rt.StackMapBuilder
   104      src := `
   105          subq    $24, %rsp
   106          movq    %rbp, 16(%rsp)
   107          leaq    16(%rsp), %rbp
   108          
   109          movq    $` + fmt.Sprintf("%p", mkpointer) + `, %r12
   110          callq   %r12`
   111      if runtime.Version() < "go1.17" { src += `
   112          movq    (%rsp), %rax`
   113      }
   114      src += `
   115          movq    %rax, 8(%rsp)
   116          movq    $0x123, (%rsp)
   117          movq    $` + fmt.Sprintf("%p", collect) + `, %r12
   118          callq   %r12
   119          movq    16(%rsp), %rbp
   120          addq    $24, %rsp
   121          ret`
   122      require.NoError(t, asm.Assemble(src))
   123      smb.AddField(true)
   124      cc := asm.Code()
   125      smb1 := new(rt.StackMapBuilder)
   126      smb1.AddField(false)
   127      fp := Loader(cc).Load("test_with_stackmap", rt.Frame {
   128          SpTab: []rt.Stack {
   129              { Sp:  0, Nb: 4 },
   130              { Sp: 24, Nb: uintptr(len(cc) - 5) },
   131              { Sp:  0, Nb: 0 },
   132          },
   133          ArgSize   : 0,
   134          ArgPtrs   : smb1.Build(), // Build() should be called on non-empty Builder,
   135                                    // otherwise mallocgc will allocate less than enough
   136                                    // which leads to fatal error when run with -race
   137          LocalPtrs : smb.Build(),
   138      })
   139      dumpfunction(*(*func())(unsafe.Pointer(&fp)))
   140      println("enter function")
   141      (*(*func())(unsafe.Pointer(&fp)))()
   142      println("leave function")
   143      collect()
   144  }
   145  
   146  //go:linkname step runtime.step
   147  //goland:noinspection GoUnusedParameter
   148  func step(p []byte, pc *uintptr, val *int32, first bool) (newp []byte, ok bool)
   149  
   150  func dumpfunction(f interface{}) {
   151      fp := rt.FuncAddr(f)
   152      fn := findfunc(uintptr(fp))
   153      var name string
   154      if runtime.Version() >= "go1.16" {
   155          name = "pctab"
   156      } else {
   157          name = "pclntable"
   158      }
   159      datap := reflect.ValueOf(fn.datap)
   160      ff, ok := datap.Type().Elem().FieldByName(name)
   161      if !ok {
   162          panic("no such field: pctab")
   163      }
   164      p := (*(*[]byte)(unsafe.Pointer(uintptr(unsafe.Pointer(fn.datap)) + ff.Offset)))[fn.pcsp:]
   165      pc := fn.entry()
   166      val := int32(-1)
   167      lastpc := uintptr(0)
   168      for {
   169          var ok bool
   170          lastpc = pc
   171          p, ok = step(p, &pc, &val, pc == fn.entry())
   172          if !ok {
   173              break
   174          }
   175          fmt.Printf("%#x = %#x\n", lastpc, val)
   176      }
   177      pc = 0
   178      lastpc -= fn.entry()
   179      for pc <= lastpc {
   180          pp := unsafe.Pointer(uintptr(fp) + pc)
   181          fx := runtime.FuncForPC(uintptr(pp))
   182          if fx.Name() != "" && fx.Entry() == uintptr(pp) {
   183              println("----", fx.Name(), "----")
   184          }
   185          ins, err := x86asm.Decode(rt.BytesFrom(pp, 15, 15), 64)
   186          if err != nil {
   187              panic(err)
   188          }
   189          fmt.Printf("%#x %s\n", uintptr(pp), x86asm.GNUSyntax(ins, uint64(uintptr(pp)), func(u uint64) (string, uint64) {
   190              v := runtime.FuncForPC(uintptr(u))
   191              if v == nil {
   192                  return "", 0
   193              }
   194              return v.Name(), uint64(v.Entry())
   195          }))
   196          pc += uintptr(ins.Len)
   197      }
   198  }
   199  
   200  func TestLoader_PCSPDelta(t *testing.T) {
   201      dumpfunction(moduledataverify1)
   202  }