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 committed Jan 26, 2024
1 parent f95957d commit 3d6f99d
Show file tree
Hide file tree
Showing 5 changed files with 136 additions and 42 deletions.
68 changes: 68 additions & 0 deletions internal/auxv.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package internal

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

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

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

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
}
44 changes: 44 additions & 0 deletions internal/auxv_go121.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//go:build go1.21

package internal

import (
"errors"
"io"
_ "unsafe" // for linkname
)

//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+1 >= len(r.data) {
return 0, 0, io.EOF
}

tag, value := r.data[r.index], r.data[r.index+1]
r.index += 2
return uint64(tag), uint64(value), nil
}

func auxvReader() (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
}
13 changes: 13 additions & 0 deletions internal/auxv_pre_go121.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//go:build !go1.21

package internal

import (
"encoding/binary"
"unsafe"
)

func auxvReader() (auxvPairReader, error) {
const uintptrIs32bits = unsafe.Sizeof((uintptr)(0)) == 4
return newAuxFileReaderFromPath("/proc/self/auxv", binary.NativeEndian, uintptrIs32bits)

Check failure on line 12 in internal/auxv_pre_go121.go

View workflow job for this annotation

GitHub Actions / Run tests on arm64

undefined: newAuxFileReaderFromPath

Check failure on line 12 in internal/auxv_pre_go121.go

View workflow job for this annotation

GitHub Actions / Run tests on arm64

undefined: binary.NativeEndian

Check failure on line 12 in internal/auxv_pre_go121.go

View workflow job for this annotation

GitHub Actions / Run tests on arm64

undefined: newAuxFileReaderFromPath

Check failure on line 12 in internal/auxv_pre_go121.go

View workflow job for this annotation

GitHub Actions / Run tests on arm64

undefined: binary.NativeEndian

Check failure on line 12 in internal/auxv_pre_go121.go

View workflow job for this annotation

GitHub Actions / Run tests on previous stable Go

undefined: newAuxFileReaderFromPath

Check failure on line 12 in internal/auxv_pre_go121.go

View workflow job for this annotation

GitHub Actions / Run tests on previous stable Go

undefined: binary.NativeEndian

Check failure on line 12 in internal/auxv_pre_go121.go

View workflow job for this annotation

GitHub Actions / Run tests on previous stable Go

undefined: newAuxFileReaderFromPath

Check failure on line 12 in internal/auxv_pre_go121.go

View workflow job for this annotation

GitHub Actions / Run tests on previous stable Go

undefined: binary.NativeEndian

Check failure on line 12 in internal/auxv_pre_go121.go

View workflow job for this annotation

GitHub Actions / Run tests on pre-built kernel (6.7)

undefined: newAuxFileReaderFromPath

Check failure on line 12 in internal/auxv_pre_go121.go

View workflow job for this annotation

GitHub Actions / Run tests on pre-built kernel (6.7)

undefined: binary.NativeEndian

Check failure on line 12 in internal/auxv_pre_go121.go

View workflow job for this annotation

GitHub Actions / Run tests on pre-built kernel (6.7)

undefined: newAuxFileReaderFromPath

Check failure on line 12 in internal/auxv_pre_go121.go

View workflow job for this annotation

GitHub Actions / Run tests on pre-built kernel (6.7)

undefined: binary.NativeEndian

Check failure on line 12 in internal/auxv_pre_go121.go

View workflow job for this annotation

GitHub Actions / Run tests on pre-built kernel (5.10)

undefined: newAuxFileReaderFromPath

Check failure on line 12 in internal/auxv_pre_go121.go

View workflow job for this annotation

GitHub Actions / Run tests on pre-built kernel (5.10)

undefined: binary.NativeEndian

Check failure on line 12 in internal/auxv_pre_go121.go

View workflow job for this annotation

GitHub Actions / Run tests on pre-built kernel (5.10)

undefined: newAuxFileReaderFromPath

Check failure on line 12 in internal/auxv_pre_go121.go

View workflow job for this annotation

GitHub Actions / Run tests on pre-built kernel (5.10)

undefined: binary.NativeEndian

Check failure on line 12 in internal/auxv_pre_go121.go

View workflow job for this annotation

GitHub Actions / Run tests on pre-built kernel (4.14)

undefined: newAuxFileReaderFromPath

Check failure on line 12 in internal/auxv_pre_go121.go

View workflow job for this annotation

GitHub Actions / Run tests on pre-built kernel (4.14)

undefined: binary.NativeEndian

Check failure on line 12 in internal/auxv_pre_go121.go

View workflow job for this annotation

GitHub Actions / Run tests on pre-built kernel (4.14)

undefined: newAuxFileReaderFromPath

Check failure on line 12 in internal/auxv_pre_go121.go

View workflow job for this annotation

GitHub Actions / Run tests on pre-built kernel (4.14)

undefined: binary.NativeEndian
}
45 changes: 7 additions & 38 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,15 @@ 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 := auxvReader()
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,33 +48,9 @@ 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) {
func vdsoMemoryAddress(r auxvPairReader) (uintptr, error) {
// See https://elixir.bootlin.com/linux/v6.5.5/source/include/uapi/linux/auxvec.h
const (
_AT_NULL = 0 // End of vector
Expand All @@ -91,7 +60,7 @@ func vdsoMemoryAddress(r io.Reader, order binary.ByteOrder, uintptrIs32bits bool
// 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 3d6f99d

Please sign in to comment.