// Copyright 2023 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.

package pprof

import (
	"internal/byteorder"
	"os"
	"unsafe"
)

func isExecutable(protection int32) bool {
	return (protection&_VM_PROT_EXECUTE) != 0 && (protection&_VM_PROT_READ) != 0
}

// machVMInfo uses the mach_vm_region region system call to add mapping entries
// for the text region of the running process.
func machVMInfo(addMapping func(lo, hi, offset uint64, file, buildID string)) bool {
	added := false
	var addr uint64 = 0x1
	for {
		var memRegionSize uint64
		var info machVMRegionBasicInfoData
		// Get the first address and page size.
		kr := mach_vm_region(
			&addr,
			&memRegionSize,
			unsafe.Pointer(&info))
		if kr != 0 {
			if kr == _MACH_SEND_INVALID_DEST {
				// No more memory regions.
				return true
			}
			return added // return true if at least one mapping was added
		}
		if isExecutable(info.Protection) {
			// NOTE: the meaning/value of Offset is unclear. However,
			// this likely doesn't matter as the text segment's file
			// offset is usually 0.
			addMapping(addr,
				addr+memRegionSize,
				byteorder.LEUint64(info.Offset[:]),
				regionFilename(addr),
				"")
			added = true
		}
		addr += memRegionSize
	}
}

func regionFilename(address uint64) string {
	buf := make([]byte, _MAXPATHLEN)
	r := proc_regionfilename(
		os.Getpid(),
		address,
		unsafe.SliceData(buf),
		int64(cap(buf)))
	if r == 0 {
		return ""
	}
	return string(buf[:r])
}

// mach_vm_region and proc_regionfilename are implemented by
// the runtime package (runtime/sys_darwin.go).
//
//go:noescape
func mach_vm_region(address, region_size *uint64, info unsafe.Pointer) int32

//go:noescape
func proc_regionfilename(pid int, address uint64, buf *byte, buflen int64) int32
