Skip to content

Commit

Permalink
add support for reading auxv from Go runtime
Browse files Browse the repository at this point in the history
Extract auxv related code to own file, and use runtime provided auxv fetching
function starting from go 1.21. The old file based code is kept for old
go version and tests.

Signed-off-by: Paul Cacheux <paul.cacheux@datadoghq.com>
  • Loading branch information
paulcacheux authored and lmb committed Feb 8, 2024
1 parent b3432fb commit fbff7db
Show file tree
Hide file tree
Showing 4 changed files with 175 additions and 48 deletions.
60 changes: 60 additions & 0 deletions internal/auxv.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package internal

import (
"errors"
"io"
_ "unsafe"
)

type auxvPairReader interface {
Close() error
ReadAuxvPair() (uint64, uint64, error)
}

// See https://elixir.bootlin.com/linux/v6.5.5/source/include/uapi/linux/auxvec.h
const (
_AT_NULL = 0 // End of vector
_AT_SYSINFO_EHDR = 33 // Offset to vDSO blob in process image
)

//go:linkname runtime_getAuxv runtime.getAuxv
func runtime_getAuxv() []uintptr

type auxvRuntimeReader struct {
data []uintptr
index int
}

func (r *auxvRuntimeReader) Close() error {
return nil
}

func (r *auxvRuntimeReader) ReadAuxvPair() (uint64, uint64, error) {
if r.index >= len(r.data)+2 {
return 0, 0, io.EOF
}

// we manually add the (_AT_NULL, _AT_NULL) pair at the end
// that is not provided by the go runtime
var tag, value uintptr
if r.index+1 < len(r.data) {
tag, value = r.data[r.index], r.data[r.index+1]
} else {
tag, value = _AT_NULL, _AT_NULL
}
r.index += 2
return uint64(tag), uint64(value), nil
}

func newAuxvRuntimeReader() (auxvPairReader, error) {
data := runtime_getAuxv()

if len(data)%2 != 0 {
return nil, errors.New("malformed auxv passed from runtime")
}

return &auxvRuntimeReader{
data: data,
index: 0,
}, nil
}
105 changes: 105 additions & 0 deletions internal/auxv_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package internal

import (
"encoding/binary"
"errors"
"fmt"
"os"
"testing"
"unsafe"

"github.com/cilium/ebpf/internal/unix"
)

type auxvFileReader struct {
file *os.File
order binary.ByteOrder
uintptrIs32bits bool
}

func (r *auxvFileReader) Close() error {
return r.file.Close()
}

type auxvPair32 struct {
Tag, Value uint32
}

type auxvPair64 struct {
Tag, Value uint64
}

func (r *auxvFileReader) ReadAuxvPair() (tag, value uint64, _ error) {
if r.uintptrIs32bits {
var aux auxvPair32
if err := binary.Read(r.file, r.order, &aux); err != nil {
return 0, 0, fmt.Errorf("reading auxv entry: %w", err)
}
return uint64(aux.Tag), uint64(aux.Value), nil
}

var aux auxvPair64
if err := binary.Read(r.file, r.order, &aux); err != nil {
return 0, 0, fmt.Errorf("reading auxv entry: %w", err)
}
return aux.Tag, aux.Value, nil
}

func newAuxFileReader(path string, order binary.ByteOrder, uintptrIs32bits bool) (auxvPairReader, error) {
// Read data from the auxiliary vector, which is normally passed directly
// to the process. Go does not expose that data before go 1.21, so we must read it from procfs.
// https://man7.org/linux/man-pages/man3/getauxval.3.html
av, err := os.Open(path)
if errors.Is(err, unix.EACCES) {
return nil, fmt.Errorf("opening auxv: %w (process may not be dumpable due to file capabilities)", err)
}
if err != nil {
return nil, fmt.Errorf("opening auxv: %w", err)
}

return &auxvFileReader{
file: av,
order: order,
uintptrIs32bits: uintptrIs32bits,
}, nil
}

func newDefaultAuxvFileReader() (auxvPairReader, error) {
const uintptrIs32bits = unsafe.Sizeof((uintptr)(0)) == 4
return newAuxFileReader("/proc/self/auxv", NativeEndian, uintptrIs32bits)
}

func TestAuxvBothSourcesEqual(t *testing.T) {
runtimeBased, err := newAuxvRuntimeReader()
if err != nil {
t.Fatal(err)
}
fileBased, err := newDefaultAuxvFileReader()
if err != nil {
t.Fatal(err)
}

for {
runtimeTag, runtimeValue, err := runtimeBased.ReadAuxvPair()
if err != nil {
t.Fatal(err)
}

fileTag, fileValue, err := fileBased.ReadAuxvPair()
if err != nil {
t.Fatal(err)
}

if runtimeTag != fileTag {
t.Errorf("mismatching tags: runtime=%v, file=%v", runtimeTag, fileTag)
}

if runtimeValue != fileValue {
t.Errorf("mismatching values: runtime=%v, file=%v", runtimeValue, fileValue)
}

if runtimeTag == _AT_NULL {
break
}
}
}
50 changes: 6 additions & 44 deletions internal/vdso.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
"io"
"math"
"os"
"unsafe"

"github.com/cilium/ebpf/internal/unix"
)
Expand All @@ -20,21 +19,14 @@ var (
// vdsoVersion returns the LINUX_VERSION_CODE embedded in the vDSO library
// linked into the current process image.
func vdsoVersion() (uint32, error) {
const uintptrIs32bits = unsafe.Sizeof((uintptr)(0)) == 4

// Read data from the auxiliary vector, which is normally passed directly
// to the process. Go does not expose that data, so we must read it from procfs.
// https://man7.org/linux/man-pages/man3/getauxval.3.html
av, err := os.Open("/proc/self/auxv")
if errors.Is(err, unix.EACCES) {
return 0, fmt.Errorf("opening auxv: %w (process may not be dumpable due to file capabilities)", err)
}
av, err := newAuxvRuntimeReader()
if err != nil {
return 0, fmt.Errorf("opening auxv: %w", err)
return 0, err
}

defer av.Close()

vdsoAddr, err := vdsoMemoryAddress(av, NativeEndian, uintptrIs32bits)
vdsoAddr, err := vdsoMemoryAddress(av)
if err != nil {
return 0, fmt.Errorf("finding vDSO memory address: %w", err)
}
Expand All @@ -55,43 +47,13 @@ func vdsoVersion() (uint32, error) {
return c, nil
}

type auxvPair32 struct {
Tag, Value uint32
}

type auxvPair64 struct {
Tag, Value uint64
}

func readAuxvPair(r io.Reader, order binary.ByteOrder, uintptrIs32bits bool) (tag, value uint64, _ error) {
if uintptrIs32bits {
var aux auxvPair32
if err := binary.Read(r, order, &aux); err != nil {
return 0, 0, fmt.Errorf("reading auxv entry: %w", err)
}
return uint64(aux.Tag), uint64(aux.Value), nil
}

var aux auxvPair64
if err := binary.Read(r, order, &aux); err != nil {
return 0, 0, fmt.Errorf("reading auxv entry: %w", err)
}
return aux.Tag, aux.Value, nil
}

// vdsoMemoryAddress returns the memory address of the vDSO library
// linked into the current process image. r is an io.Reader into an auxv blob.
func vdsoMemoryAddress(r io.Reader, order binary.ByteOrder, uintptrIs32bits bool) (uintptr, error) {
// See https://elixir.bootlin.com/linux/v6.5.5/source/include/uapi/linux/auxvec.h
const (
_AT_NULL = 0 // End of vector
_AT_SYSINFO_EHDR = 33 // Offset to vDSO blob in process image
)

func vdsoMemoryAddress(r auxvPairReader) (uintptr, error) {
// Loop through all tag/value pairs in auxv until we find `AT_SYSINFO_EHDR`,
// the address of a page containing the virtual Dynamic Shared Object (vDSO).
for {
tag, value, err := readAuxvPair(r, order, uintptrIs32bits)
tag, value, err := r.ReadAuxvPair()
if err != nil {
return 0, err
}
Expand Down
8 changes: 4 additions & 4 deletions internal/vdso_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@ func TestAuxvVDSOMemoryAddress(t *testing.T) {
{"auxv32le.bin", true, 0xb7fc3000},
} {
t.Run(testcase.source, func(t *testing.T) {
av, err := os.Open("testdata/" + testcase.source)
av, err := newAuxFileReader("testdata/"+testcase.source, binary.LittleEndian, testcase.is32bit)
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { av.Close() })

addr, err := vdsoMemoryAddress(av, binary.LittleEndian, testcase.is32bit)
addr, err := vdsoMemoryAddress(av)
if err != nil {
t.Fatal(err)
}
Expand All @@ -39,13 +39,13 @@ func TestAuxvVDSOMemoryAddress(t *testing.T) {

func TestAuxvNoVDSO(t *testing.T) {
// Copy of auxv.bin with the vDSO pointer removed.
av, err := os.Open("testdata/auxv64le_no_vdso.bin")
av, err := newAuxFileReader("testdata/auxv64le_no_vdso.bin", binary.LittleEndian, false)
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { av.Close() })

_, err = vdsoMemoryAddress(av, binary.LittleEndian, false)
_, err = vdsoMemoryAddress(av)
if want, got := errAuxvNoVDSO, err; !errors.Is(got, want) {
t.Fatalf("expected error '%v', got: %v", want, got)
}
Expand Down

0 comments on commit fbff7db

Please sign in to comment.