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 }