// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

//go:build linux && (386 || amd64 || arm || arm64 || loong64 || mips64 || mips64le || ppc64 || ppc64le || riscv64 || s390x)

package runtime

import 

// Look up symbols in the Linux vDSO.

// This code was originally based on the sample Linux vDSO parser at
// https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/tree/tools/testing/selftests/vDSO/parse_vdso.c

// This implements the ELF dynamic linking spec at
// http://sco.com/developers/gabi/latest/ch5.dynamic.html

// The version section is documented at
// https://refspecs.linuxfoundation.org/LSB_3.2.0/LSB-Core-generic/LSB-Core-generic/symversion.html

const (
	_AT_SYSINFO_EHDR = 33

	_PT_LOAD    = 1 /* Loadable program segment */
	_PT_DYNAMIC = 2 /* Dynamic linking information */

	_DT_NULL     = 0          /* Marks end of dynamic section */
	_DT_HASH     = 4          /* Dynamic symbol hash table */
	_DT_STRTAB   = 5          /* Address of string table */
	_DT_SYMTAB   = 6          /* Address of symbol table */
	_DT_GNU_HASH = 0x6ffffef5 /* GNU-style dynamic symbol hash table */
	_DT_VERSYM   = 0x6ffffff0
	_DT_VERDEF   = 0x6ffffffc

	_VER_FLG_BASE = 0x1 /* Version definition of file itself */

	_SHN_UNDEF = 0 /* Undefined section */

	_SHT_DYNSYM = 11 /* Dynamic linker symbol table */

	_STT_FUNC = 2 /* Symbol is a code object */

	_STT_NOTYPE = 0 /* Symbol type is not specified */

	_STB_GLOBAL = 1 /* Global symbol */
	_STB_WEAK   = 2 /* Weak symbol */

	_EI_NIDENT = 16

	// Maximum indices for the array types used when traversing the vDSO ELF structures.
	// Computed from architecture-specific max provided by vdso_linux_*.go
	vdsoSymTabSize     = vdsoArrayMax / unsafe.Sizeof(elfSym{})
	vdsoDynSize        = vdsoArrayMax / unsafe.Sizeof(elfDyn{})
	vdsoSymStringsSize = vdsoArrayMax     // byte
	vdsoVerSymSize     = vdsoArrayMax / 2 // uint16
	vdsoHashSize       = vdsoArrayMax / 4 // uint32

	// vdsoBloomSizeScale is a scaling factor for gnuhash tables which are uint32 indexed,
	// but contain uintptrs
	vdsoBloomSizeScale = unsafe.Sizeof(uintptr(0)) / 4 // uint32
)

/* How to extract and insert information held in the st_info field.  */
func _ELF_ST_BIND( byte) byte { return  >> 4 }
func _ELF_ST_TYPE( byte) byte { return  & 0xf }

type vdsoSymbolKey struct {
	name    string
	symHash uint32
	gnuHash uint32
	ptr     *uintptr
}

type vdsoVersionKey struct {
	version string
	verHash uint32
}

type vdsoInfo struct {
	valid bool

	/* Load information */
	loadAddr   uintptr
	loadOffset uintptr /* loadAddr - recorded vaddr */

	/* Symbol table */
	symtab     *[vdsoSymTabSize]elfSym
	symstrings *[vdsoSymStringsSize]byte
	chain      []uint32
	bucket     []uint32
	symOff     uint32
	isGNUHash  bool

	/* Version table */
	versym *[vdsoVerSymSize]uint16
	verdef *elfVerdef
}

// see vdso_linux_*.go for vdsoSymbolKeys[] and vdso*Sym vars

func vdsoInitFromSysinfoEhdr( *vdsoInfo,  *elfEhdr) {
	.valid = false
	.loadAddr = uintptr(unsafe.Pointer())

	 := unsafe.Pointer(.loadAddr + uintptr(.e_phoff))

	// We need two things from the segment table: the load offset
	// and the dynamic table.
	var  bool
	var  *[vdsoDynSize]elfDyn
	for  := uint16(0);  < .e_phnum; ++ {
		 := (*elfPhdr)(add(, uintptr()*unsafe.Sizeof(elfPhdr{})))
		switch .p_type {
		case _PT_LOAD:
			if ! {
				 = true
				.loadOffset = .loadAddr + uintptr(.p_offset-.p_vaddr)
			}

		case _PT_DYNAMIC:
			 = (*[vdsoDynSize]elfDyn)(unsafe.Pointer(.loadAddr + uintptr(.p_offset)))
		}
	}

	if ! ||  == nil {
		return // Failed
	}

	// Fish out the useful bits of the dynamic table.

	var ,  *[vdsoHashSize]uint32
	.symstrings = nil
	.symtab = nil
	.versym = nil
	.verdef = nil
	for  := 0; [].d_tag != _DT_NULL; ++ {
		 := &[]
		 := .loadOffset + uintptr(.d_val)
		switch .d_tag {
		case _DT_STRTAB:
			.symstrings = (*[vdsoSymStringsSize]byte)(unsafe.Pointer())
		case _DT_SYMTAB:
			.symtab = (*[vdsoSymTabSize]elfSym)(unsafe.Pointer())
		case _DT_HASH:
			 = (*[vdsoHashSize]uint32)(unsafe.Pointer())
		case _DT_GNU_HASH:
			 = (*[vdsoHashSize]uint32)(unsafe.Pointer())
		case _DT_VERSYM:
			.versym = (*[vdsoVerSymSize]uint16)(unsafe.Pointer())
		case _DT_VERDEF:
			.verdef = (*elfVerdef)(unsafe.Pointer())
		}
	}

	if .symstrings == nil || .symtab == nil || ( == nil &&  == nil) {
		return // Failed
	}

	if .verdef == nil {
		.versym = nil
	}

	if  != nil {
		// Parse the GNU hash table header.
		 := [0]
		.symOff = [1]
		 := [2]
		.bucket = [4+*uint32(vdsoBloomSizeScale):][:]
		.chain = [4+*uint32(vdsoBloomSizeScale)+:]
		.isGNUHash = true
	} else {
		// Parse the hash table header.
		 := [0]
		 := [1]
		.bucket = [2 : 2+]
		.chain = [2+ : 2++]
	}

	// That's all we need.
	.valid = true
}

func vdsoFindVersion( *vdsoInfo,  *vdsoVersionKey) int32 {
	if !.valid {
		return 0
	}

	 := .verdef
	for {
		if .vd_flags&_VER_FLG_BASE == 0 {
			 := (*elfVerdaux)(add(unsafe.Pointer(), uintptr(.vd_aux)))
			if .vd_hash == .verHash && .version == gostringnocopy(&.symstrings[.vda_name]) {
				return int32(.vd_ndx & 0x7fff)
			}
		}

		if .vd_next == 0 {
			break
		}
		 = (*elfVerdef)(add(unsafe.Pointer(), uintptr(.vd_next)))
	}

	return -1 // cannot match any version
}

func vdsoParseSymbols( *vdsoInfo,  int32) {
	if !.valid {
		return
	}

	 := func( uint32,  vdsoSymbolKey) bool {
		 := &.symtab[]
		 := _ELF_ST_TYPE(.st_info)
		 := _ELF_ST_BIND(.st_info)
		// On ppc64x, VDSO functions are of type _STT_NOTYPE.
		if  != _STT_FUNC &&  != _STT_NOTYPE ||  != _STB_GLOBAL &&  != _STB_WEAK || .st_shndx == _SHN_UNDEF {
			return false
		}
		if .name != gostringnocopy(&.symstrings[.st_name]) {
			return false
		}
		// Check symbol version.
		if .versym != nil &&  != 0 && int32(.versym[]&0x7fff) !=  {
			return false
		}

		*.ptr = .loadOffset + uintptr(.st_value)
		return true
	}

	if !.isGNUHash {
		// Old-style DT_HASH table.
		for ,  := range vdsoSymbolKeys {
			if len(.bucket) > 0 {
				for  := .bucket[.symHash%uint32(len(.bucket))];  != 0;  = .chain[] {
					if (, ) {
						break
					}
				}
			}
		}
		return
	}

	// New-style DT_GNU_HASH table.
	for ,  := range vdsoSymbolKeys {
		 := .bucket[.gnuHash%uint32(len(.bucket))]
		if  < .symOff {
			continue
		}
		for ; ; ++ {
			 := .chain[-.symOff]
			if |1 == .gnuHash|1 {
				// Found a hash match.
				if (, ) {
					break
				}
			}
			if &1 != 0 {
				// End of chain.
				break
			}
		}
	}
}

func vdsoauxv(,  uintptr) {
	switch  {
	case _AT_SYSINFO_EHDR:
		if  == 0 {
			// Something went wrong
			return
		}
		var  vdsoInfo
		// TODO(rsc): I don't understand why the compiler thinks info escapes
		// when passed to the three functions below.
		 := (*vdsoInfo)(noescape(unsafe.Pointer(&)))
		vdsoInitFromSysinfoEhdr(, (*elfEhdr)(unsafe.Pointer()))
		vdsoParseSymbols(, vdsoFindVersion(, &vdsoLinuxVersion))
	}
}

// vdsoMarker reports whether PC is on the VDSO page.
//
//go:nosplit
func inVDSOPage( uintptr) bool {
	for ,  := range vdsoSymbolKeys {
		if *.ptr != 0 {
			 := *.ptr &^ (physPageSize - 1)
			return  >=  &&  < +physPageSize
		}
	}
	return false
}