Apa yang perlu Anda ketahui jika Anda ingin memanggil fungsi Go dari assembler

Anda telah mengalami area kode asm yang sangat berbulu.
Saran pertama saya adalah jangan mencoba menelepon dari assembler ke Go. - Ian Lance Taylor

Selama kode assembler Anda melakukan sesuatu yang sederhana, semuanya terlihat bagus.


Segera setelah Anda memiliki tugas untuk memanggil fungsi dari kode assembler Go, salah satu tips pertama yang akan Anda berikan: jangan lakukan ini.


Tetapi bagaimana jika Anda benar-benar membutuhkannya? Kalau begitu, tolong, di bawah kucing.



Konvensi panggilan


Semuanya dimulai dengan fakta bahwa Anda perlu memahami cara menyampaikan argumen ke fungsi dan cara menerima hasilnya .


Saya sarankan Anda membiasakan diri dengan fungsi Go dalam bahasa assembly , di mana sebagian besar informasi yang kami butuhkan dijelaskan dengan jelas.


Biasanya, konvensi panggilan bervariasi dari platform ke platform, karena set register yang tersedia dapat bervariasi. Kami hanya akan mempertimbangkan GOARCH=amd64, tetapi dalam kasus Go, konvensi berbeda tidak begitu signifikan.


Berikut adalah beberapa fitur dari konvensi pemanggilan fungsi di Go:


  • Semua argumen dilewatkan melalui tumpukan, kecuali untuk "konteks" pada penutupan, ia dapat diakses melalui register DX(% rdx).
  • ( arguments).
  • .
  • . , .

, . .



, register-based calling convention. Go.


, :


  • , .
  • 0(SP), 8(SP) ( 8 ), .
  • n(SP), n — . int64, 16(SP).

.


package main

func asmfunc(x int32) (int32, int32)

func gofunc(a1 int64, a2, a3 int32) (int32, int32) {
    return int32(a1) + a2, int32(a1) + a3
}

func main() {
    v1, v2 := asmfunc(10)
    println(v1, v2) // => 3, 11
}

// func asmfunc(x int32) (int32, int32)
TEXT ·asmfunc(SB), 0, $24-12
  MOVL x+0(FP), AX
  MOVQ $1, 0(SP)  //   (a1 int64)
  MOVL $2, 8(SP)  //   (a2 int32)
  MOVL AX, 12(SP) //   (a3 int32)
  CALL ·gofunc(SB)
  MOVL 16(SP), AX //   
  MOVL 20(SP), CX //   
  MOVL AX, ret+8(FP)  //   
  MOVL CX, ret+12(FP) //   
  RET

$24-16 (locals=24 bytes, args=16 bytes)

          0     8     12    16     20     SP
locals=24 [a1:8][a2:4][a3:4][ret:4][ret:4]
(ret    asmfunc,   gofunc)

        0    4          8      12     FP
args=16 [x:4][padding:4][ret:4][ret:4]
(ret    main,   asmfunc)

, 4 . , , (8 amd64).


, . — int32, — int64, offset 8, 4, reflect.TypeOf(int64(0)).Align() 8.


, FP go vet.


stackmap


-.


package foo

import (
    "fmt"
    "testing"
)

func foo(ptr *object)

type object struct {
    x, y, z int64
}

func printPtr(ptr *object) {
    fmt.Println(*ptr)
}

func TestFoo(t *testing.T) {
    foo(&object{x: 11, y: 22, z: 33})
}

TEXT ·foo(SB), 0, $8-8
        MOVQ ptr+0(FP), AX
        MOVQ AX, 0(SP)
        CALL ·printPtr(SB)
        RET

, - stackmap:


=== RUN   TestFoo
runtime: frame <censored> untyped locals 0xc00008ff38+0x8
fatal error: missing stackmap

, , GC stackmaps. Go , .


stub ( Go ). , stackmap , .


, stackmap (), NO_LOCAL_POINTERS ( ).


NO_LOCAL_POINTERS


TestFoo :


#include "funcdata.h"

TEXT ·foo(SB), 0, $8-8
        NO_LOCAL_POINTERS
        MOVQ ptr+0(FP), AX
        MOVQ AX, 0(SP)
        CALL ·printPtr(SB)
        RET

, .


, ? , , "" , , , , ?


, heap, , . , , . GC "" , , .


, , "" (escapes to heap) escape analysis, , .


NO_LOCAL_POINTERS: , , , GC . .


Go non-cooperative preemption, , .


Go. , go:nosplit, , , NO_LOCAL_POINTERS .


GO_ARGS


, Go prototype, GO_ARGS.


GO_ARGSfuncdata.h, NO_LOCAL_POINTERS. , stackmap Go .


, stackmap . args_stackmap . : , stackmap.


GO_RESULTS_INITIALIZED


Go , ( ) GO_RESULTS_INITIALIZED .


:


// func getg() interface{}
TEXT ·getg(SB), NOSPLIT, $32-16
  //     .
  //      .
  MOVQ $0, ret_type+0(FP)
  MOVQ $0, ret_data+8(FP)
  GO_RESULTS_INITIALIZED
  //    ...
  RET

, , -.


GitHub.


Go JIT-


Go .


Garbage collector Go , , , , , Go JIT'.


, . , , .


calljit-v1


// file jit.go
package main

import (
    "log"
    "reflect"
    "syscall"
    "unsafe"
)

func main() {
    a := funcAddr(goFunc)

    code := []byte{
        // MOVQ addr(goFunc), AX
        0xb8, byte(a), byte(a >> 8), byte(a >> 16), byte(a >> 24),
        // CALL AX
        0xff, 0xd0,
        // RET
        0xc3,
    }

    executable, err := mmapExecutable(len(code))
    if err != nil {
        log.Panicf("mmap: %v", err)
    }
    copy(executable, code)
    calljit(&executable[0])
}

func calljit(code *byte)

func goFunc() {
    println("called from JIT")
}

func mmapExecutable(length int) ([]byte, error) {
    const prot = syscall.PROT_READ | syscall.PROT_WRITE | syscall.PROT_EXEC
    const flags = syscall.MAP_PRIVATE | syscall.MAP_ANON
    return mmapLinux(0, uintptr(length), prot, flags, 0, 0)
}

func mmapLinux(addr, length, prot, flags, fd, off uintptr) ([]byte, error) {
    ptr, _, err := syscall.Syscall6(
        syscall.SYS_MMAP,
        addr, length, prot, flags, fd, offset)
    if err != 0 {
        return nil, err
    }
    slice := *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{
        Data: ptr,
        Len:  int(length),
        Cap:  int(length),
    }))
    return slice, nil
}

func funcAddr(fn interface{}) uintptr {
    type emptyInterface struct {
        typ   uintptr
        value *uintptr
    }
    e := (*emptyInterface)(unsafe.Pointer(&fn))
    return *e.value
}

// file jit_amd64.s
TEXT ·calljit(SB), 0, $0-8
        MOVQ code+0(FP), AX
        JMP AX



, ( ):


$ go build -o jit . && ./jit
called from JIT

goFunc, JIT-:


 func goFunc() {
    println("called from JIT")
+   runtime.GC()
 }

:


$ go build -o jit . && ./jit
called from JIT
runtime: unexpected return pc for main.goFunc called from 0x7f9465f7c007
stack: frame={sp:0xc00008ced0, fp:0xc00008cef0} stack=[0xc00008c000,0xc00008d000)
000000c00008cdd0:  0000000000000000  00007f94681f7558 
000000c00008cde0:  000000c000029270  000000000000000b 
... (+ more)

: unexpected return pc for main.goFunc called from 0x7f9465f7c007, 0x7f9465f7c007 — JIT-. , runtime.


, , FP BP return address , .


Go runtime , JIT-, , .


calljit , , Go . , Go , , Go (calljit).


#include "funcdata.h"

TEXT ·calljit(SB), 0, $8-8
        NO_LOCAL_POINTERS
        MOVQ code+0(FP), AX
        JMP AX
callgo:
        CALL CX
        JMP (SP)

:


  • 8 , return address JIT .
  • NO_LOCAL_POINTERS - , CALL.

calljit , , , Go . , CX , [rsp] — , .


, callgo. , . main():


a := funcAddr(goFunc)
j := funcAddr(calljit) + 36

code := []byte{
    // MOVQ funcAddr(goFunc), CX
    0x48, 0xc7, 0xc1, byte(a), byte(a >> 8), byte(a >> 16), byte(a >> 24),
    // MOVQ funcAddr(calljit), DI
    0x48, 0xc7, 0xc7, byte(j), byte(j >> 8), byte(j >> 16), byte(j >> 24),
    // LEAQ 6(PC), SI
    0x48, 0x8d, 0x35, (4 + 2), 0, 0, 0,
    // MOVQ SI, (SP)
    0x48, 0x89, 0x34, 0x24,
    // JMP DI
    0xff, 0xe7,

    // ADDQ $framesize, SP
    0x48, 0x83, 0xc4, (8 + 8),
    // RET
    0xc3,
}

CX, callgo DI, SI , [rsp]. 4+2LEAQ .


, , ADDQ. 8 8 BP.


calljit:


   return address  callgo
|
|       Go    BP   
|       |
0(SP)   8(SP)    16(SP)    24(SP)
[empty] [prevBP] [retaddr] [arg1:code]
|             /  |         |
|            /   |           calljit (caller frame)
|           /    |      
|          /       CALL   calljit
|         /
calljit frame, 16 bytes     

runtime.GC():


$ go build -o jit . && ./jit
called from JIT

Go . , — , .


, , . Go.


Go Internal ABI


Go Internal ABI — .


Go , , . ABI, , .


:


  1. .
  2. .

calling convention ABI0, ABIInternal.


Go -S, , ABIInternal , ABI0:



ABIInternal , ABI1, . ABIInternal calling convention .


, .


, Go . , .





Hub-


, . , , , . .


All Articles