github.com/cloudwego/frugal@v0.1.7/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/chenzhuoyu/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      fp := Loader(cc).Load("test_with_stackmap", rt.Frame {
   126          SpTab: []rt.Stack {
   127              { Sp:  0, Nb: 4 },
   128              { Sp: 24, Nb: uintptr(len(cc) - 5) },
   129              { Sp:  0, Nb: 0 },
   130          },
   131          ArgSize   : 0,
   132          ArgPtrs   : new(rt.StackMapBuilder).Build(),
   133          LocalPtrs : smb.Build(),
   134      })
   135      dumpfunction(*(*func())(unsafe.Pointer(&fp)))
   136      println("enter function")
   137      (*(*func())(unsafe.Pointer(&fp)))()
   138      println("leave function")
   139      collect()
   140  }
   141  
   142  //go:linkname step runtime.step
   143  //goland:noinspection GoUnusedParameter
   144  func step(p []byte, pc *uintptr, val *int32, first bool) (newp []byte, ok bool)
   145  
   146  func dumpfunction(f interface{}) {
   147      fp := rt.FuncAddr(f)
   148      fn := findfunc(uintptr(fp))
   149      var name string
   150      if runtime.Version() >= "go1.16" {
   151          name = "pctab"
   152      } else {
   153          name = "pclntable"
   154      }
   155      datap := reflect.ValueOf(fn.datap)
   156      ff, ok := datap.Type().Elem().FieldByName(name)
   157      if !ok {
   158          panic("no such field: pctab")
   159      }
   160      p := (*(*[]byte)(unsafe.Pointer(uintptr(unsafe.Pointer(fn.datap)) + ff.Offset)))[fn.pcsp:]
   161      pc := fn.entry()
   162      val := int32(-1)
   163      lastpc := uintptr(0)
   164      for {
   165          var ok bool
   166          lastpc = pc
   167          p, ok = step(p, &pc, &val, pc == fn.entry())
   168          if !ok {
   169              break
   170          }
   171          fmt.Printf("%#x = %#x\n", lastpc, val)
   172      }
   173      pc = 0
   174      lastpc -= fn.entry()
   175      for pc <= lastpc {
   176          pp := unsafe.Pointer(uintptr(fp) + pc)
   177          fx := runtime.FuncForPC(uintptr(pp))
   178          if fx.Name() != "" && fx.Entry() == uintptr(pp) {
   179              println("----", fx.Name(), "----")
   180          }
   181          ins, err := x86asm.Decode(rt.BytesFrom(pp, 15, 15), 64)
   182          if err != nil {
   183              panic(err)
   184          }
   185          fmt.Printf("%#x %s\n", uintptr(pp), x86asm.GNUSyntax(ins, uint64(uintptr(pp)), func(u uint64) (string, uint64) {
   186              v := runtime.FuncForPC(uintptr(u))
   187              if v == nil {
   188                  return "", 0
   189              }
   190              return v.Name(), uint64(v.Entry())
   191          }))
   192          pc += uintptr(ins.Len)
   193      }
   194  }
   195  
   196  func TestLoader_PCSPDelta(t *testing.T) {
   197      dumpfunction(moduledataverify1)
   198  }