Go黑魔法之导出私有函数与私有变量
在Go语言中, package
中包含函数与变量通过identifier
的首字母是否大写来决定它是否可以被其它package
所访问。当一个函数或变量名称为小写字母时,默认是无法被其他package
引用的.
有没有办法突破这个限制呢?
实际上在go官方文档中已有说明, 这需要用到一个编译器指令
//go:linkname localname importpath.name
官方的解释是:
The //go:linkname directive instructs the compiler to use “importpath.name” as the object file symbol name for the variable or function declared as “localname” in the source code. Because this directive can subvert the type system and package modularity, it is only enabled in files that have imported "unsafe".
//go:linkname
指令指示编译器使用importpath.name
作为源代码中声明为localname
的变量或函数的对象文件符号名。因为这个指令可以破坏类型系统和包的模块化,所以它只在导入“不安全”的文件中启用。
我的理解就是使用localname
作为当前package
本地函数或变量名称,关联到importpath.name
实际的符号引用.实际上locaname
只是一个作为链接符号,这个跟WINDOWS中的DLL符号关联有点类似.
示例
以下示例引用了runtime.sysMmap
一系列私有函数与变量,用于创建一个huagepage
共享内存区域.
package main
/*
#cgo CFLAGS:
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/signal.h>
#include <sys/time.h>
#include <sys/ucontext.h>
#include <sys/unistd.h>
#include <errno.h>
#include <signal.h>
*/
import "C"
import (
"fmt"
"os"
"encoding/hex"
"syscall"
"reflect"
_ "runtime"
"unsafe"
"os/exec"
)
const (
PROT_NONE = C.PROT_NONE
PROT_READ = C.PROT_READ
PROT_WRITE = C.PROT_WRITE
PROT_EXEC = C.PROT_EXEC
MAP_SHARED = 0x1
MAP_ANON = C.MAP_ANONYMOUS
MAP_PRIVATE = C.MAP_PRIVATE
MAP_FIXED = C.MAP_FIXED
)
//go:linkname physPageSize runtime.physPageSize
//go:linkname callCgoMmap runtime.callCgoMmap
//go:linkname callCgoMunmap runtime.callCgoMunmap
//go:linkname sysMmap runtime.sysMmap
//go:linkname sysMunmap runtime.sysMunmap
//go:linkname Mmap runtime.mmap
//go:linkname Munmap runtime.munmap
// sysMmap calls the mmap system call. It is implemented in assembly.
func sysMmap(addr unsafe.Pointer, n uintptr, prot, flags, fd int32, off uint32) (p unsafe.Pointer, err int)
// callCgoMmap calls the mmap function in the runtime/cgo package
// using the GCC calling convention. It is implemented in assembly.
func callCgoMmap(addr unsafe.Pointer, n uintptr, prot, flags, fd int32, off uint32) uintptr
// sysMunmap calls the munmap system call. It is implemented in assembly.
func sysMunmap(addr unsafe.Pointer, n uintptr)
// callCgoMunmap calls the munmap function in the runtime/cgo package
// using the GCC calling convention. It is implemented in assembly.
func callCgoMunmap(addr unsafe.Pointer, n uintptr)
func Mmap(addr unsafe.Pointer, n uintptr, prot, flags, fd int32, off uint32) (p unsafe.Pointer, err int)
func Munmap(addr unsafe.Pointer, n uintptr)
var physPageSize uintptr
const (
ENOMEM = 12
)
func GetPhysPageSize() uintptr {
return physPageSize
}
func TestHugePage() {
file := "/dev/hugepages/hp"
f, err := os.OpenFile(file, os.O_CREATE|os.O_RDWR, 0644)
if err != nil {
fmt.Printf("Open: %v", err)
return
}
var length uintptr = 2 * 1024 * 1024;
var flags int32 = MAP_SHARED;
var proto int32 = PROT_READ|PROT_WRITE;
addr, e := sysMmap(nil, length, proto, flags, int32(f.Fd()), 0)
if e != 0 {
fmt.Printf("Mmap occur error %d\n", e);
return
}
fmt.Printf("Mmap %p\n", addr);
var x reflect.SliceHeader
x.Len = 512
x.Cap = 512
x.Data = uintptr(addr)
var payload []byte = *(*[]byte)(unsafe.Pointer(&x))
stdoutDumper := hex.Dumper(os.Stdout)
defer stdoutDumper.Close()
stdoutDumper.Write(payload)
// Munmap(addr, length)
}
func main() {
var pid int
pid = syscall.Getpid()
fmt.Println("GetPhysPageSize:", GetPhysPageSize())
TestHugePage()
cmd := exec.Command("cat", str)
out, err := cmd.CombinedOutput()
if err != nil {
fmt.Printf("cmd.Run() failed with %s\n", err)
return
}
fmt.Printf("combined out:\n%s\n", string(out))
}