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 }