github.com/eh-steve/goloader@v0.0.0-20240111193454-90ff3cfdae39/mmap/manager_unix.go (about)

     1  //go:build freebsd || linux || netbsd
     2  // +build freebsd linux netbsd
     3  
     4  package mmap
     5  
     6  import (
     7  	"bytes"
     8  	"fmt"
     9  	"github.com/eh-steve/goloader/mmap/mapping"
    10  	"os"
    11  	"strconv"
    12  	"strings"
    13  )
    14  
    15  // Based on format of /proc/[pid]/maps from https://man7.org/linux/man-pages/man5/proc.5.html
    16  // Only tested on linux, but netbsd/freebsd should be the same.
    17  // TODO: OpenBSD needs to use procmap https://man.openbsd.org/procmap.1
    18  // TODO: Solaris needs to use /proc/[pid]/map with a different format
    19  // TODO: Dragonfly needs  /proc/curproc/map with a different format
    20  func getCurrentProcMaps() ([]mapping.Mapping, error) {
    21  	mapsData, err := os.ReadFile("/proc/self/maps")
    22  	if err != nil {
    23  		return nil, fmt.Errorf("could not read '/proc/self/maps': %w", err)
    24  	}
    25  	lines := bytes.Split(mapsData, []byte("\n"))
    26  	var mappings []mapping.Mapping
    27  	for i, line := range lines {
    28  		var mapping mapping.Mapping
    29  		mmapFields := strings.Fields(string(line))
    30  		if len(mmapFields) == 0 {
    31  			continue
    32  		}
    33  		if len(mmapFields) < 4 {
    34  			return nil, fmt.Errorf("got fewer than 4 fields on line %d of /proc/self/maps: %s", i, line)
    35  		}
    36  		addrRange := strings.Split(mmapFields[0], "-")
    37  		if len(addrRange) != 2 {
    38  			return nil, fmt.Errorf("got %d fields for address range on line %d (expected 2): %s", len(addrRange), i, line)
    39  		}
    40  		startAddr, err := strconv.ParseUint(addrRange[0], 16, 64)
    41  		if err != nil {
    42  			return nil, fmt.Errorf("failed to parse start address (%s) on line %d as uint64 (line: %s): %w", addrRange[0], i, line, err)
    43  		}
    44  		endAddr, err := strconv.ParseUint(addrRange[1], 16, 64)
    45  		if err != nil {
    46  			return nil, fmt.Errorf("failed to parse end address (%s) on line %d as uint64 (line: %s): %w", addrRange[1], i, line, err)
    47  		}
    48  		mapping.StartAddr = uintptr(startAddr)
    49  		mapping.EndAddr = uintptr(endAddr)
    50  		perms := mmapFields[1]
    51  		for _, char := range perms {
    52  			switch char {
    53  			case 'r':
    54  				mapping.ReadPerm = true
    55  			case 'w':
    56  				mapping.WritePerm = true
    57  			case 'x':
    58  				mapping.ExecutePerm = true
    59  			case 's':
    60  				mapping.SharedPerm = true
    61  			case 'p':
    62  				mapping.PrivatePerm = true
    63  			case '-':
    64  			default:
    65  				return nil, fmt.Errorf("got an unexpected permission bit '%s' in perms '%s'", string(char), perms)
    66  			}
    67  		}
    68  		offset, err := strconv.ParseUint(mmapFields[2], 16, 64)
    69  		if err != nil {
    70  			return nil, fmt.Errorf("failed to parse file offset (%s) on line %d as uint64 (line: %s): %w", mmapFields[2], i, line, err)
    71  		}
    72  		mapping.Offset = uintptr(offset)
    73  		mapping.Dev = mmapFields[3]
    74  		inode, err := strconv.ParseUint(mmapFields[4], 16, 64)
    75  		if err != nil {
    76  			return nil, fmt.Errorf("failed to parse inode (%s) on line %d as uint64 (line: %s): %w", mmapFields[4], i, line, err)
    77  		}
    78  		mapping.Inode = inode
    79  		if len(mmapFields) > 5 {
    80  			mapping.PathName = mmapFields[5]
    81  		}
    82  		mappings = append(mappings, mapping)
    83  	}
    84  
    85  	return mappings, nil
    86  }