Compare commits
10 Commits
3672fd455b
...
d15586390a
Author | SHA1 | Date | |
---|---|---|---|
d15586390a | |||
5b967134e8 | |||
8005624fac | |||
88f1455dd7 | |||
d800683dce | |||
baf998232a | |||
edfb1b1bb7 | |||
647e704fed | |||
afe46025f5 | |||
de5bcf9913 |
@ -1,23 +1,102 @@
|
|||||||
|
// +build !withcgo
|
||||||
|
|
||||||
package ebpf
|
package ebpf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// All constants are taken from /usr/include/linux/bpf.h
|
||||||
|
|
||||||
|
const BPF_SYSCALL = 321
|
||||||
|
|
||||||
|
type bpf_cmd int
|
||||||
|
|
||||||
|
const (
|
||||||
|
BPF_MAP_CREATE bpf_cmd = iota
|
||||||
|
BPF_MAP_LOOKUP_ELEM
|
||||||
|
BPF_MAP_UPDATE_ELEM
|
||||||
|
BPF_MAP_DELETE_ELEM
|
||||||
|
BPF_MAP_GET_NEXT_KEY
|
||||||
|
BPF_PROG_LOAD
|
||||||
|
BPF_OBJ_PIN
|
||||||
|
BPF_OBJ_GET
|
||||||
|
BPF_PROG_ATTACH
|
||||||
|
BPF_PROG_DETACH
|
||||||
|
BPF_PROG_TEST_RUN
|
||||||
|
BPF_PROG_GET_NEXT_ID
|
||||||
|
BPF_MAP_GET_NEXT_ID
|
||||||
|
BPF_PROG_GET_FD_BY_ID
|
||||||
|
BPF_MAP_GET_FD_BY_ID
|
||||||
|
BPF_OBJ_GET_INFO_BY_FD
|
||||||
|
BPF_PROG_QUERY
|
||||||
|
BPF_RAW_TRACEPOINT_OPEN
|
||||||
|
BPF_BTF_LOAD
|
||||||
|
BPF_BTF_GET_FD_BY_ID
|
||||||
|
BPF_TASK_FD_QUERY
|
||||||
|
BPF_MAP_LOOKUP_AND_DELETE_ELEM
|
||||||
|
BPF_MAP_FREEZE
|
||||||
|
)
|
||||||
|
|
||||||
|
type bpf_map_type int
|
||||||
|
|
||||||
|
const (
|
||||||
|
BPF_MAP_TYPE_UNSPEC bpf_map_type = iota
|
||||||
|
BPF_MAP_TYPE_HASH
|
||||||
|
BPF_MAP_TYPE_ARRAY
|
||||||
|
BPF_MAP_TYPE_PROG_ARRAY
|
||||||
|
BPF_MAP_TYPE_PERF_EVENT_ARRAY
|
||||||
|
BPF_MAP_TYPE_PERCPU_HASH
|
||||||
|
BPF_MAP_TYPE_PERCPU_ARRAY
|
||||||
|
BPF_MAP_TYPE_STACK_TRACE
|
||||||
|
BPF_MAP_TYPE_CGROUP_ARRAY
|
||||||
|
BPF_MAP_TYPE_LRU_HASH
|
||||||
|
BPF_MAP_TYPE_LRU_PERCPU_HASH
|
||||||
|
BPF_MAP_TYPE_LPM_TRIE
|
||||||
|
BPF_MAP_TYPE_ARRAY_OF_MAPS
|
||||||
|
BPF_MAP_TYPE_HASH_OF_MAPS
|
||||||
|
BPF_MAP_TYPE_DEVMAP
|
||||||
|
BPF_MAP_TYPE_SOCKMAP
|
||||||
|
BPF_MAP_TYPE_CPUMAP
|
||||||
|
BPF_MAP_TYPE_XSKMAP
|
||||||
|
BPF_MAP_TYPE_SOCKHASH
|
||||||
|
BPF_MAP_TYPE_CGROUP_STORAGE
|
||||||
|
BPF_MAP_TYPE_REUSEPORT_SOCKARRAY
|
||||||
|
BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE
|
||||||
|
BPF_MAP_TYPE_QUEUE
|
||||||
|
BPF_MAP_TYPE_STACK
|
||||||
|
BPF_MAP_TYPE_SK_STORAGE
|
||||||
|
)
|
||||||
|
|
||||||
|
// MapFD is a file descriptor representing a eBPF map
|
||||||
|
type MapFD uint32
|
||||||
|
|
||||||
/*
|
/*
|
||||||
#include <stdlib.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <linux/unistd.h>
|
|
||||||
#include <linux/bpf.h>
|
|
||||||
|
|
||||||
// See the bpf man page for details on the following functions
|
All methods in this file implement a syscall to bpf one way or another. We
|
||||||
|
follow the C-API given in bpf(2) and have the corresponding C-functions
|
||||||
|
embedded as comments. Those all refer to the C-function bpf(), which is a
|
||||||
|
wrapper for the syscall:
|
||||||
|
|
||||||
static int bpf(enum bpf_cmd cmd, union bpf_attr *attr, unsigned int size)
|
static int bpf(enum bpf_cmd cmd, union bpf_attr *attr, unsigned int size)
|
||||||
{
|
{
|
||||||
return syscall(__NR_bpf, cmd, attr, size);
|
return syscall(__NR_bpf, cmd, attr, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int bpf_create_map(
|
Each ouf our methods calls the syscall directly, instead.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
// CreateMap creates an eBPF map from int->uint64. The file descriptor of the
|
||||||
|
// created map is returned.
|
||||||
|
func CreateMap() (MapFD, error) {
|
||||||
|
/*
|
||||||
|
static int bpf_create_map(
|
||||||
enum bpf_map_type map_type, unsigned int key_size, unsigned int value_size,
|
enum bpf_map_type map_type, unsigned int key_size, unsigned int value_size,
|
||||||
unsigned int max_entries)
|
unsigned int max_entries)
|
||||||
{
|
{
|
||||||
union bpf_attr attr = {
|
union bpf_attr attr = {
|
||||||
.map_type = map_type,
|
.map_type = map_type,
|
||||||
.key_size = key_size,
|
.key_size = key_size,
|
||||||
@ -25,21 +104,75 @@ static int bpf_create_map(
|
|||||||
.max_entries = max_entries};
|
.max_entries = max_entries};
|
||||||
|
|
||||||
return bpf(BPF_MAP_CREATE, &attr, sizeof(attr));
|
return bpf(BPF_MAP_CREATE, &attr, sizeof(attr));
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
create_attr := struct {
|
||||||
|
map_type uint32
|
||||||
|
key_size uint32
|
||||||
|
value_size uint32
|
||||||
|
max_entries uint32
|
||||||
|
}{
|
||||||
|
map_type: uint32(BPF_MAP_TYPE_HASH),
|
||||||
|
key_size: 4,
|
||||||
|
value_size: 8,
|
||||||
|
max_entries: 64,
|
||||||
|
}
|
||||||
|
|
||||||
|
r, _, err := syscall.Syscall(
|
||||||
|
BPF_SYSCALL,
|
||||||
|
uintptr(BPF_MAP_CREATE),
|
||||||
|
uintptr(unsafe.Pointer(&create_attr)),
|
||||||
|
unsafe.Sizeof(create_attr),
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != 0 {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return MapFD(r), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
int bpf_get_next_key(int fd, const void *key, void *next_key)
|
func (mfd MapFD) bpf_get_next_key(key *int, next_key *int) error {
|
||||||
{
|
/*
|
||||||
|
int bpf_get_next_key(int fd, const void *key, void *next_key)
|
||||||
|
{
|
||||||
union bpf_attr attr = {
|
union bpf_attr attr = {
|
||||||
.map_fd = fd,
|
.map_fd = fd,
|
||||||
.key = (__u64) (unsigned long) key,
|
.key = (__u64) (unsigned long) key,
|
||||||
.next_key = (__u64) (unsigned long) next_key};
|
.next_key = (__u64) (unsigned long) next_key};
|
||||||
|
|
||||||
return bpf(BPF_MAP_GET_NEXT_KEY, &attr, sizeof(attr));
|
return bpf(BPF_MAP_GET_NEXT_KEY, &attr, sizeof(attr));
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
next_attr := struct {
|
||||||
|
map_fd MapFD
|
||||||
|
key uint64
|
||||||
|
next_key uint64
|
||||||
|
}{
|
||||||
|
map_fd: mfd,
|
||||||
|
key: uint64(uintptr(unsafe.Pointer(key))),
|
||||||
|
next_key: uint64(uintptr(unsafe.Pointer(next_key))),
|
||||||
|
}
|
||||||
|
|
||||||
|
r, _, err := syscall.Syscall(
|
||||||
|
BPF_SYSCALL,
|
||||||
|
uintptr(BPF_MAP_GET_NEXT_KEY),
|
||||||
|
uintptr(unsafe.Pointer(&next_attr)),
|
||||||
|
unsafe.Sizeof(next_attr),
|
||||||
|
)
|
||||||
|
|
||||||
|
if r != 0 {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (mfd MapFD) bpf_lookup_elem(key *int, value *uint64) error {
|
||||||
int bpf_lookup_elem(int fd, const void *key, void *value)
|
/*
|
||||||
{
|
int bpf_lookup_elem(int fd, const void *key, void *value)
|
||||||
|
{
|
||||||
union bpf_attr attr = {
|
union bpf_attr attr = {
|
||||||
.map_fd = fd,
|
.map_fd = fd,
|
||||||
.key = (__u64) (unsigned long) key,
|
.key = (__u64) (unsigned long) key,
|
||||||
@ -47,10 +180,93 @@ int bpf_lookup_elem(int fd, const void *key, void *value)
|
|||||||
};
|
};
|
||||||
|
|
||||||
return bpf(BPF_MAP_LOOKUP_ELEM, &attr, sizeof(attr));
|
return bpf(BPF_MAP_LOOKUP_ELEM, &attr, sizeof(attr));
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
lookup_attr := struct {
|
||||||
|
map_fd MapFD
|
||||||
|
key uint64
|
||||||
|
value uint64
|
||||||
|
}{
|
||||||
|
map_fd: mfd,
|
||||||
|
key: uint64(uintptr(unsafe.Pointer(key))),
|
||||||
|
value: uint64(uintptr(unsafe.Pointer(value))),
|
||||||
|
}
|
||||||
|
|
||||||
|
r, _, err := syscall.Syscall(
|
||||||
|
BPF_SYSCALL,
|
||||||
|
uintptr(BPF_MAP_LOOKUP_ELEM),
|
||||||
|
uintptr(unsafe.Pointer(&lookup_attr)),
|
||||||
|
unsafe.Sizeof(lookup_attr),
|
||||||
|
)
|
||||||
|
|
||||||
|
if r != 0 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
int bpf_update_elem(int fd, const void *key, const void *value, uint64_t flags)
|
// GetMap gets the key/value pairs from the specified eBPF map as a Go map
|
||||||
{
|
func (mfd MapFD) GetMap() (map[int]uint64, error) {
|
||||||
|
retMap := make(map[int]uint64)
|
||||||
|
|
||||||
|
var (
|
||||||
|
key, next int
|
||||||
|
value uint64
|
||||||
|
)
|
||||||
|
|
||||||
|
for {
|
||||||
|
err := mfd.bpf_get_next_key(&key, &next)
|
||||||
|
if err != nil {
|
||||||
|
if err == syscall.ENOENT {
|
||||||
|
// The provided key was the last element. We're done iterating.
|
||||||
|
return retMap, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("bpf_get_next_key failed with error %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = mfd.bpf_lookup_elem(&next, &value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("bpf_lookup_elem failed with error %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
retMap[next] = value
|
||||||
|
|
||||||
|
key = next
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
BPF_ANY = iota
|
||||||
|
BPF_NOEXIST
|
||||||
|
BPF_EXIST
|
||||||
|
BPF_F_LOCK
|
||||||
|
)
|
||||||
|
|
||||||
|
// Add puts the (key, value) into the eBPF map, only if the key does not exist
|
||||||
|
// yet in the map. It returns an error otherwise.
|
||||||
|
func (mfd MapFD) Add(key int, value uint64) error {
|
||||||
|
return mfd.updateElement(key, value, BPF_NOEXIST)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Change changes the value to an existing key in the eBPF map. It returns an
|
||||||
|
// error otherwise.
|
||||||
|
func (mfd MapFD) Change(key int, value uint64) error {
|
||||||
|
return mfd.updateElement(key, value, BPF_EXIST)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set puts the (key, value) into the eBPF map. It will create or overwrite an
|
||||||
|
// existing entry for that key.
|
||||||
|
func (mfd MapFD) Set(key int, value uint64) error {
|
||||||
|
return mfd.updateElement(key, value, BPF_ANY)
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateElement is the low level wrapper to bpf_update_elem, used from Add(),
|
||||||
|
// Set() and Change().
|
||||||
|
func (mfd MapFD) updateElement(key int, value uint64, flag uint64) error {
|
||||||
|
/*
|
||||||
|
int bpf_update_elem(int fd, const void *key, const void *value, uint64_t flags)
|
||||||
|
{
|
||||||
union bpf_attr attr = {
|
union bpf_attr attr = {
|
||||||
.map_fd = fd,
|
.map_fd = fd,
|
||||||
.key = (__u64) (unsigned long) key,
|
.key = (__u64) (unsigned long) key,
|
||||||
@ -59,91 +275,30 @@ int bpf_update_elem(int fd, const void *key, const void *value, uint64_t flags)
|
|||||||
};
|
};
|
||||||
|
|
||||||
return bpf(BPF_MAP_UPDATE_ELEM, &attr, sizeof(attr));
|
return bpf(BPF_MAP_UPDATE_ELEM, &attr, sizeof(attr));
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
*/
|
update_attr := struct {
|
||||||
import "C"
|
map_fd MapFD
|
||||||
import (
|
key uint64
|
||||||
"fmt"
|
value uint64
|
||||||
"syscall"
|
flags uint64
|
||||||
"unsafe"
|
}{
|
||||||
)
|
map_fd: mfd,
|
||||||
|
key: uint64(uintptr(unsafe.Pointer(&key))),
|
||||||
// MapFD is a file descriptor representing a eBPF map
|
value: uint64(uintptr(unsafe.Pointer(&value))),
|
||||||
type MapFD int
|
flags: flag,
|
||||||
|
|
||||||
// CreateMap creates an eBPF map from int->uint64. The file descriptor of the
|
|
||||||
// created map is returned.
|
|
||||||
func CreateMap() (MapFD, error) {
|
|
||||||
|
|
||||||
r, errno := C.bpf_create_map(C.BPF_MAP_TYPE_HASH, 4, 8, 64)
|
|
||||||
|
|
||||||
if r == -1 {
|
|
||||||
return MapFD(r), fmt.Errorf("bpf_create_map with errno %d", errno)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return MapFD(r), nil
|
r, _, err := syscall.Syscall(
|
||||||
}
|
BPF_SYSCALL,
|
||||||
|
uintptr(BPF_MAP_UPDATE_ELEM),
|
||||||
|
uintptr(unsafe.Pointer(&update_attr)),
|
||||||
|
unsafe.Sizeof(update_attr),
|
||||||
|
)
|
||||||
|
|
||||||
// GetMap gets the key/value pairs from the specified eBPF map as a Go map
|
if r != 0 || err != 0 {
|
||||||
func (mfd MapFD) GetMap() (map[int]uint64, error) {
|
return fmt.Errorf("couldn't update element: %s", err.Error())
|
||||||
retMap := make(map[int]uint64)
|
|
||||||
|
|
||||||
mapFDC := C.int(mfd)
|
|
||||||
keyC := C.int(0)
|
|
||||||
nextKeyC := C.int(0)
|
|
||||||
valC := C.uint64_t(0)
|
|
||||||
|
|
||||||
for {
|
|
||||||
r, errno := C.bpf_get_next_key(mapFDC, unsafe.Pointer(&keyC), unsafe.Pointer(&nextKeyC))
|
|
||||||
if r == -1 {
|
|
||||||
if errno == syscall.ENOENT {
|
|
||||||
// The provided key was the last element. We're done iterating.
|
|
||||||
return retMap, nil
|
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("bpf_get_next_key failed with errno %d", errno)
|
|
||||||
}
|
|
||||||
|
|
||||||
r = C.bpf_lookup_elem(mapFDC, unsafe.Pointer(&nextKeyC), unsafe.Pointer(&valC))
|
|
||||||
if r == -1 {
|
|
||||||
return nil, fmt.Errorf("bpf_lookup_elem failed")
|
|
||||||
}
|
|
||||||
retMap[int(nextKeyC)] = uint64(valC)
|
|
||||||
|
|
||||||
keyC = nextKeyC
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add puts the (key, value) into the eBPF map, only if the key does not exist
|
|
||||||
// yet in the map. It returns an error otherwise.
|
|
||||||
func (mfd MapFD) Add(key int, value uint64) error {
|
|
||||||
return mfd.updateElement(key, value, C.BPF_NOEXIST)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Change changes the value to an existing key in the eBPF map. It returns an
|
|
||||||
// error otherwise.
|
|
||||||
func (mfd MapFD) Change(key int, value uint64) error {
|
|
||||||
return mfd.updateElement(key, value, C.BPF_EXIST)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set puts the (key, value) into the eBPF map. It will create or overwrite an
|
|
||||||
// existing entry for that key.
|
|
||||||
func (mfd MapFD) Set(key int, value uint64) error {
|
|
||||||
return mfd.updateElement(key, value, C.BPF_ANY)
|
|
||||||
}
|
|
||||||
|
|
||||||
// updateElement is the low level wrapper to bpf_update_elem, used from Add(),
|
|
||||||
// Set() and Change().
|
|
||||||
func (mfd MapFD) updateElement(key int, value uint64, flag C.uint64_t) error {
|
|
||||||
|
|
||||||
r, errno := C.bpf_update_elem(C.int(mfd),
|
|
||||||
unsafe.Pointer(&key),
|
|
||||||
unsafe.Pointer(&value),
|
|
||||||
flag)
|
|
||||||
|
|
||||||
if r == -1 {
|
|
||||||
return fmt.Errorf("bpf_update_elem failed with errno %d", errno)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
151
GetRuntimeAddresses/ebpf/ebpf_cgo.go
Normal file
151
GetRuntimeAddresses/ebpf/ebpf_cgo.go
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
// +build withcgo
|
||||||
|
|
||||||
|
package ebpf
|
||||||
|
|
||||||
|
/*
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <linux/unistd.h>
|
||||||
|
#include <linux/bpf.h>
|
||||||
|
|
||||||
|
// See the bpf man page for details on the following functions
|
||||||
|
|
||||||
|
static int bpf(enum bpf_cmd cmd, union bpf_attr *attr, unsigned int size)
|
||||||
|
{
|
||||||
|
return syscall(__NR_bpf, cmd, attr, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int bpf_create_map(
|
||||||
|
enum bpf_map_type map_type, unsigned int key_size, unsigned int value_size,
|
||||||
|
unsigned int max_entries)
|
||||||
|
{
|
||||||
|
union bpf_attr attr = {
|
||||||
|
.map_type = map_type,
|
||||||
|
.key_size = key_size,
|
||||||
|
.value_size = value_size,
|
||||||
|
.max_entries = max_entries};
|
||||||
|
|
||||||
|
return bpf(BPF_MAP_CREATE, &attr, sizeof(attr));
|
||||||
|
}
|
||||||
|
|
||||||
|
int bpf_get_next_key(int fd, const void *key, void *next_key)
|
||||||
|
{
|
||||||
|
union bpf_attr attr = {
|
||||||
|
.map_fd = fd,
|
||||||
|
.key = (__u64) (unsigned long) key,
|
||||||
|
.next_key = (__u64) (unsigned long) next_key};
|
||||||
|
|
||||||
|
return bpf(BPF_MAP_GET_NEXT_KEY, &attr, sizeof(attr));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int bpf_lookup_elem(int fd, const void *key, void *value)
|
||||||
|
{
|
||||||
|
union bpf_attr attr = {
|
||||||
|
.map_fd = fd,
|
||||||
|
.key = (__u64) (unsigned long) key,
|
||||||
|
.value = (__u64) (unsigned long) value,
|
||||||
|
};
|
||||||
|
|
||||||
|
return bpf(BPF_MAP_LOOKUP_ELEM, &attr, sizeof(attr));
|
||||||
|
}
|
||||||
|
|
||||||
|
int bpf_update_elem(int fd, const void *key, const void *value, uint64_t flags)
|
||||||
|
{
|
||||||
|
union bpf_attr attr = {
|
||||||
|
.map_fd = fd,
|
||||||
|
.key = (__u64) (unsigned long) key,
|
||||||
|
.value = (__u64) (unsigned long) value,
|
||||||
|
.flags = flags,
|
||||||
|
};
|
||||||
|
|
||||||
|
return bpf(BPF_MAP_UPDATE_ELEM, &attr, sizeof(attr));
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MapFD is a file descriptor representing a eBPF map
|
||||||
|
type MapFD int
|
||||||
|
|
||||||
|
// CreateMap creates an eBPF map from int->uint64. The file descriptor of the
|
||||||
|
// created map is returned.
|
||||||
|
func CreateMap() (MapFD, error) {
|
||||||
|
|
||||||
|
r, errno := C.bpf_create_map(C.BPF_MAP_TYPE_HASH, 4, 8, 64)
|
||||||
|
|
||||||
|
if r == -1 {
|
||||||
|
return MapFD(r), fmt.Errorf("bpf_create_map with errno %d", errno)
|
||||||
|
}
|
||||||
|
|
||||||
|
return MapFD(r), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMap gets the key/value pairs from the specified eBPF map as a Go map
|
||||||
|
func (mfd MapFD) GetMap() (map[int]uint64, error) {
|
||||||
|
retMap := make(map[int]uint64)
|
||||||
|
|
||||||
|
mapFDC := C.int(mfd)
|
||||||
|
keyC := C.int(0)
|
||||||
|
nextKeyC := C.int(0)
|
||||||
|
valC := C.uint64_t(0)
|
||||||
|
|
||||||
|
for {
|
||||||
|
r, errno := C.bpf_get_next_key(mapFDC, unsafe.Pointer(&keyC), unsafe.Pointer(&nextKeyC))
|
||||||
|
if r == -1 {
|
||||||
|
if errno == syscall.ENOENT {
|
||||||
|
// The provided key was the last element. We're done iterating.
|
||||||
|
return retMap, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("bpf_get_next_key failed with errno %d", errno)
|
||||||
|
}
|
||||||
|
|
||||||
|
r = C.bpf_lookup_elem(mapFDC, unsafe.Pointer(&nextKeyC), unsafe.Pointer(&valC))
|
||||||
|
if r == -1 {
|
||||||
|
return nil, fmt.Errorf("bpf_lookup_elem failed")
|
||||||
|
}
|
||||||
|
retMap[int(nextKeyC)] = uint64(valC)
|
||||||
|
|
||||||
|
keyC = nextKeyC
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add puts the (key, value) into the eBPF map, only if the key does not exist
|
||||||
|
// yet in the map. It returns an error otherwise.
|
||||||
|
func (mfd MapFD) Add(key int, value uint64) error {
|
||||||
|
return mfd.updateElement(key, value, C.BPF_NOEXIST)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Change changes the value to an existing key in the eBPF map. It returns an
|
||||||
|
// error otherwise.
|
||||||
|
func (mfd MapFD) Change(key int, value uint64) error {
|
||||||
|
return mfd.updateElement(key, value, C.BPF_EXIST)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set puts the (key, value) into the eBPF map. It will create or overwrite an
|
||||||
|
// existing entry for that key.
|
||||||
|
func (mfd MapFD) Set(key int, value uint64) error {
|
||||||
|
return mfd.updateElement(key, value, C.BPF_ANY)
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateElement is the low level wrapper to bpf_update_elem, used from Add(),
|
||||||
|
// Set() and Change().
|
||||||
|
func (mfd MapFD) updateElement(key int, value uint64, flag C.uint64_t) error {
|
||||||
|
|
||||||
|
r, errno := C.bpf_update_elem(C.int(mfd),
|
||||||
|
unsafe.Pointer(&key),
|
||||||
|
unsafe.Pointer(&value),
|
||||||
|
flag)
|
||||||
|
|
||||||
|
if r == -1 {
|
||||||
|
return fmt.Errorf("bpf_update_elem failed with errno %d", errno)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -4,6 +4,7 @@ import (
|
|||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/optimyze-interviews/OezguerKesim/GetRuntimeAddresses/ebpf"
|
"github.com/optimyze-interviews/OezguerKesim/GetRuntimeAddresses/ebpf"
|
||||||
"github.com/optimyze-interviews/OezguerKesim/GetRuntimeAddresses/symbolyze"
|
"github.com/optimyze-interviews/OezguerKesim/GetRuntimeAddresses/symbolyze"
|
||||||
@ -28,11 +29,14 @@ func main() {
|
|||||||
|
|
||||||
scanner := symbolyze.NewScanner(*symbol, *glob)
|
scanner := symbolyze.NewScanner(*symbol, *glob)
|
||||||
scanner.OnFound(mapFD.Set)
|
scanner.OnFound(mapFD.Set)
|
||||||
if *debug {
|
scanner.Debug(*debug)
|
||||||
scanner.DebugOn()
|
|
||||||
}
|
|
||||||
|
|
||||||
err = scanner.Run()
|
go scanner.RunEvery(time.Second)
|
||||||
|
|
||||||
|
time.Sleep(10 * time.Second)
|
||||||
|
scanner.Stop()
|
||||||
|
|
||||||
|
err = scanner.Errors()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Failed to run the symbolyze scanner: %s", err)
|
fmt.Printf("Failed to run the symbolyze scanner: %s", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
26
GetRuntimeAddresses/symbolyze/doc.go
Normal file
26
GetRuntimeAddresses/symbolyze/doc.go
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
// Copyright note...
|
||||||
|
/*
|
||||||
|
Package symbolyze provides a mechanism to search for all occurences of a
|
||||||
|
certain symbol in certain mmap'ed ELF binaries of all running processes.
|
||||||
|
|
||||||
|
NewScanner(symbol string, glob string) will setup a Scanner, which will search
|
||||||
|
for the given symbol name in all mmap'ed ELF-files that match the given
|
||||||
|
glob-pattern. The glob-pattern is a shell file name pattern, see
|
||||||
|
filepath.Match.
|
||||||
|
|
||||||
|
The scanner should be populated with callback functions via calls to OnFound(),
|
||||||
|
before starting the scan by calling Run().
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
scanner := symbolyze.NewScanner("_PyRuntime", "*python3*")
|
||||||
|
|
||||||
|
scanner.OnFound(func(pid int, offset uint64) error {
|
||||||
|
fmt.Println("found symbol in", pid, "in offset", offset")
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
err := scanner.Run()
|
||||||
|
|
||||||
|
*/
|
||||||
|
package symbolyze
|
@ -15,7 +15,7 @@ import (
|
|||||||
// test-binary `simple` that is linked against python3.7. If this fails, make
|
// test-binary `simple` that is linked against python3.7. If this fails, make
|
||||||
// necessary adjustments to the Makefile and/or your environment and try again.
|
// necessary adjustments to the Makefile and/or your environment and try again.
|
||||||
func buildSimple() error {
|
func buildSimple() error {
|
||||||
cmd := exec.Command("make")
|
cmd := exec.Command("make", "-s")
|
||||||
cmd.Dir = "testdata"
|
cmd.Dir = "testdata"
|
||||||
cmd.Stdout = os.Stdout
|
cmd.Stdout = os.Stdout
|
||||||
cmd.Stderr = os.Stderr
|
cmd.Stderr = os.Stderr
|
||||||
@ -115,5 +115,6 @@ func extractOffsetWithGdb(pid int, t *testing.T) (offset uint64, err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0, fmt.Errorf("Symbol not found with gdb")
|
return 0, fmt.Errorf("[31mSymbol not found with gdb.[0m\n" +
|
||||||
|
"[35mMaybe /proc/sys/kernel/yama/ptrace_scope must be set to 0? Or try to run the test as root![0m")
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Scanner represents an engine for scanning for a specific symbol in all
|
// Scanner represents an engine for scanning for a specific symbol in all
|
||||||
@ -24,34 +25,60 @@ import (
|
|||||||
// /proc. Whenever a match is found, all observers will be called with the
|
// /proc. Whenever a match is found, all observers will be called with the
|
||||||
// (pid, offset), concurrently.
|
// (pid, offset), concurrently.
|
||||||
type Scanner struct {
|
type Scanner struct {
|
||||||
|
rwmutex
|
||||||
|
|
||||||
symbol string
|
symbol string
|
||||||
pathglob string
|
pathglob string
|
||||||
cache map[string]uint64 // Contains (pathname, offset)
|
cache map[string]uint64 // Contains (pathname, offset)
|
||||||
observers []Observer // Callbacks
|
observers []Observer // Callbacks
|
||||||
|
|
||||||
logger // Embedded logger
|
|
||||||
|
|
||||||
// Instead of using a boolean to indicate debugging, we use function
|
// Instead of using a boolean to indicate debugging, we use function
|
||||||
// members. This way we can populate them with noop-functions in the
|
// members. This way we can populate them with noop-functions in the
|
||||||
// non-debug case and not polute the code with if-statements.
|
// non-debug case and not polute the code with if-statements.
|
||||||
debugf func(format string, v ...interface{})
|
debugf func(format string, v ...interface{})
|
||||||
debugln func(v ...interface{})
|
debugln func(v ...interface{})
|
||||||
|
logger // Embedded logger
|
||||||
|
|
||||||
err error // error state of the scanner.
|
errors errors
|
||||||
|
|
||||||
|
ticker *time.Ticker // Used to run the scanner repeatedly
|
||||||
|
}
|
||||||
|
|
||||||
|
type errors []error
|
||||||
|
|
||||||
|
func (e errors) Error() string {
|
||||||
|
switch len(e) {
|
||||||
|
case 0:
|
||||||
|
return "nil"
|
||||||
|
case 1:
|
||||||
|
return e[0].Error()
|
||||||
|
default:
|
||||||
|
list := make([]string, len(e))
|
||||||
|
for i := range e {
|
||||||
|
list[i] = e[i].Error()
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("multiple errors:\n%s", strings.Join(list, "\n"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// We use a lowercase type alias for *log.Logger so that we can embedd it in
|
// We use a lowercase type alias for *log.Logger so that we can embedd it in
|
||||||
// Scanner without exporting it.
|
// Scanner without exporting it.
|
||||||
type logger = *log.Logger
|
type logger = *log.Logger
|
||||||
|
type rwmutex = sync.RWMutex
|
||||||
|
|
||||||
// An Observer is a callback that can be registerd with Scanner.OnFound. It
|
var (
|
||||||
|
nodebugf = func(format string, v ...interface{}) {}
|
||||||
|
nodebugln = func(v ...interface{}) {}
|
||||||
|
)
|
||||||
|
|
||||||
|
// An Observer is a callback that can be registered with Scanner.OnFound. It
|
||||||
// will be called with a pid and an offset. Observers are called concurrently.
|
// will be called with a pid and an offset. Observers are called concurrently.
|
||||||
// They have to be thread-safe.
|
// They have to be thread-safe.
|
||||||
type Observer func(pid int, offset uint64) error
|
type Observer func(pid int, offset uint64) error
|
||||||
|
|
||||||
// NewScanner returns a new Scanner that scans all running processes for the
|
// NewScanner returns a new Scanner that scans all running processes for the
|
||||||
// given symbol name in all memory-mapped files matching the given pathglob.
|
// given symbol name in all memory-mapped files matching the given pathglob.
|
||||||
// To be useful, one or more Observer functions should be registerd with
|
// To be useful, one or more Observer functions should be registered with
|
||||||
// Scanner.OnFound(). The scanning starts with a call of Scanner.Run().
|
// Scanner.OnFound(). The scanning starts with a call of Scanner.Run().
|
||||||
func NewScanner(symbol, pathglob string) *Scanner {
|
func NewScanner(symbol, pathglob string) *Scanner {
|
||||||
return &Scanner{
|
return &Scanner{
|
||||||
@ -62,62 +89,97 @@ func NewScanner(symbol, pathglob string) *Scanner {
|
|||||||
logger: log.New(os.Stderr, "[symbolyze] ", log.Ltime|log.Lmicroseconds),
|
logger: log.New(os.Stderr, "[symbolyze] ", log.Ltime|log.Lmicroseconds),
|
||||||
|
|
||||||
// debugging is off per default.
|
// debugging is off per default.
|
||||||
debugf: func(string, ...interface{}) {},
|
debugf: nodebugf,
|
||||||
debugln: func(...interface{}) {},
|
debugln: nodebugln,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Debug sets the scanner into debugging mode. It must called only once before
|
// Debug sets the scanner into debugging mode. It must be called only once
|
||||||
// a call to Scanner.Run().
|
// before a call to Scanner.Run().
|
||||||
func (S *Scanner) DebugOn() {
|
func (S *Scanner) Debug(on bool) {
|
||||||
|
S.Lock()
|
||||||
|
defer S.Unlock()
|
||||||
|
|
||||||
|
if on {
|
||||||
// Use the embedded *log.Logger for debugging.
|
// Use the embedded *log.Logger for debugging.
|
||||||
S.debugf = S.Printf
|
S.debugf = S.Printf
|
||||||
S.debugln = S.Println
|
S.debugln = S.Println
|
||||||
S.debugln("starting in debug-mode")
|
S.debugln("starting in debug-mode")
|
||||||
|
} else {
|
||||||
|
S.debugf = nodebugf
|
||||||
|
S.debugln = nodebugln
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// setErrorf puts the Scanner into an error state with the given error
|
// setErrorf puts the Scanner into an error state with the given error
|
||||||
// statement. It also logs the error.
|
// statement. It also logs the error. setErrorf is not thread-safe.
|
||||||
func (S *Scanner) setErrorf(format string, a ...interface{}) {
|
func (S *Scanner) setErrorf(format string, a ...interface{}) {
|
||||||
S.err = fmt.Errorf(format, a...)
|
S.Lock()
|
||||||
|
S.errors = append(S.errors, fmt.Errorf(format, a...))
|
||||||
|
S.Unlock()
|
||||||
S.Printf(format, a...)
|
S.Printf(format, a...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// OnFound puts an Observer function into the interal queue. The functions are
|
func (S *Scanner) HasErrors() bool {
|
||||||
// called in sequence in their own goroutine whenever the scanner finds the
|
S.RLock()
|
||||||
// symbol in the a running program. That implies that an Observer has to be
|
defer S.RUnlock()
|
||||||
// thread-safe. Errors from the observers will be logged.
|
return len(S.errors) > 0
|
||||||
//
|
}
|
||||||
// Calling OnFound is not thread-safe.
|
|
||||||
func (S *Scanner) OnFound(fun Observer) {
|
func (S *Scanner) Errors() error {
|
||||||
S.observers = append(S.observers, fun)
|
S.RLock()
|
||||||
|
defer S.RUnlock()
|
||||||
|
if len(S.errors) == 0 {
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
e2 := make(errors, len(S.errors))
|
||||||
|
copy(e2, S.errors)
|
||||||
|
return e2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (S *Scanner) addError(err error) {
|
||||||
|
S.Lock()
|
||||||
|
defer S.Unlock()
|
||||||
|
S.errors = append(S.errors, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnFound puts Observer functions at the end of the internal queue. All
|
||||||
|
// Observer functions are called in sequence in their own goroutine whenever
|
||||||
|
// the scanner finds the symbol in a running program. That implies that an
|
||||||
|
// Observer has to be thread-safe. Errors from the observers will be logged.
|
||||||
|
func (S *Scanner) OnFound(fun ...Observer) {
|
||||||
|
S.Lock()
|
||||||
|
defer S.Unlock()
|
||||||
|
|
||||||
|
S.observers = append(S.observers, fun...)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run starts the scanning process. It scans the maps file all processes in
|
// Run starts the scanning process. It scans the entries of all /proc/NNN/maps
|
||||||
// /proc for pathnames that match the provided pathglob and that are ELF
|
// files for pathnames that match the provided path-glob and are executables or
|
||||||
// executables or shared libraries. It searches for the provided symbol in
|
// shared libraries in ELF format. It searches for the provided symbol in
|
||||||
// those files and calls the registered Observer functions concurrently with
|
// those files and calls the registered Observer functions, concurrently, with
|
||||||
// the pid and offset of the symbol.
|
// the pid and offset of the symbol.
|
||||||
//
|
//
|
||||||
// Run will return an error if it couldn't read the proc filesystem. Otherwise
|
// Run will return an error if it couldn't read the proc filesystem. Otherwise
|
||||||
// it will try to continue to loop over all pids, writing potential errors to
|
// it will try to continue to loop over all pids, writing potential errors to
|
||||||
// the console. Errors from the observer functions are logged.
|
// the console. Errors from the observer functions are logged.
|
||||||
func (S *Scanner) Run() error {
|
func (S *Scanner) Run() error {
|
||||||
if S.err != nil {
|
if S.HasErrors() {
|
||||||
return S.err
|
return S.Errors()
|
||||||
}
|
}
|
||||||
|
|
||||||
proc, err := os.Open("/proc")
|
proc, err := os.Open("/proc")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
S.setErrorf("Failed to open /proc: %v\n", err)
|
S.setErrorf("Failed to open /proc: %v\n", err)
|
||||||
return S.err
|
return S.Errors()
|
||||||
}
|
}
|
||||||
|
|
||||||
infos, err := proc.Readdir(-1)
|
infos, err := proc.Readdir(-1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
S.setErrorf("Failed to read /proc: %v\n", err)
|
S.setErrorf("Failed to read /proc: %v\n", err)
|
||||||
return S.err
|
return S.Errors()
|
||||||
}
|
}
|
||||||
|
|
||||||
proc.Close()
|
proc.Close()
|
||||||
@ -132,7 +194,7 @@ func (S *Scanner) Run() error {
|
|||||||
continue
|
continue
|
||||||
} else if pid, err := strconv.Atoi(pid_s); err != nil {
|
} else if pid, err := strconv.Atoi(pid_s); err != nil {
|
||||||
continue
|
continue
|
||||||
} else if offset, found := S.searchSymbolIn(pid); !found {
|
} else if offset, found := S.searchSymbolInPid(pid); !found {
|
||||||
continue
|
continue
|
||||||
} else {
|
} else {
|
||||||
// Call the observers with (pid, offset), in the
|
// Call the observers with (pid, offset), in the
|
||||||
@ -143,8 +205,7 @@ func (S *Scanner) Run() error {
|
|||||||
go func() {
|
go func() {
|
||||||
err = observer(pid, offset)
|
err = observer(pid, offset)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
S.Printf("S.observer[%d](%d, %d) error: %v", n, pid, offset, err)
|
S.addError(fmt.Errorf("S.observer[%d](%d, %d) error: %v", n, pid, offset, err))
|
||||||
// TODO: accumulate errors from all Observers.
|
|
||||||
}
|
}
|
||||||
wg.Done()
|
wg.Done()
|
||||||
}()
|
}()
|
||||||
@ -154,10 +215,31 @@ func (S *Scanner) Run() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
wg.Wait() // Wait for all observers to finish
|
wg.Wait() // Wait for all observers to finish
|
||||||
return S.err
|
return S.Errors()
|
||||||
}
|
}
|
||||||
|
|
||||||
// searchSymbolIn loops over the entries in /proc/<pid>/maps and searches for
|
// RunEvery() starts a scanning process and repeats at the given time step.
|
||||||
|
func (S *Scanner) RunEvery(step time.Duration) {
|
||||||
|
S.ticker = time.NewTicker(step)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-S.ticker.C:
|
||||||
|
err := S.Run()
|
||||||
|
if err != nil {
|
||||||
|
S.Println(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (S *Scanner) Stop() {
|
||||||
|
if S.ticker != nil {
|
||||||
|
S.debugln("Stopping ticker")
|
||||||
|
S.ticker.Stop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// searchSymbolInPid loops over the entries in /proc/<pid>/maps and searches for
|
||||||
// the symbol in the mapped files.
|
// the symbol in the mapped files.
|
||||||
//
|
//
|
||||||
// The current implementation makes the following assumptions:
|
// The current implementation makes the following assumptions:
|
||||||
@ -167,12 +249,12 @@ func (S *Scanner) Run() error {
|
|||||||
// 4. The symbol is present at most in one mapped file at the same time.
|
// 4. The symbol is present at most in one mapped file at the same time.
|
||||||
//
|
//
|
||||||
// It returns the offsets in memory of the running program, if found.
|
// It returns the offsets in memory of the running program, if found.
|
||||||
func (S *Scanner) searchSymbolIn(pid int) (offset uint64, found bool) {
|
func (S *Scanner) searchSymbolInPid(pid int) (offset uint64, found bool) {
|
||||||
|
|
||||||
path := filepath.Join("/proc", strconv.Itoa(pid), "maps")
|
path := filepath.Join("/proc", strconv.Itoa(pid), "maps")
|
||||||
maps, err := os.Open(path)
|
maps, err := os.Open(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
S.Printf("%v\n", err)
|
S.debugf("%v\n", err)
|
||||||
return 0, false
|
return 0, false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -230,7 +312,7 @@ func (S *Scanner) searchSymbolIn(pid int) (offset uint64, found bool) {
|
|||||||
// Finally, find the symbol in the binary. If found,
|
// Finally, find the symbol in the binary. If found,
|
||||||
// findSymbol returns the offset of the symbol in memory,
|
// findSymbol returns the offset of the symbol in memory,
|
||||||
// taking alignment into account.
|
// taking alignment into account.
|
||||||
memOffset, found := S.findSymbol(pathname)
|
memOffset, found := S.findSymbolInELF(pathname)
|
||||||
if !found {
|
if !found {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -242,20 +324,23 @@ func (S *Scanner) searchSymbolIn(pid int) (offset uint64, found bool) {
|
|||||||
return 0, false
|
return 0, false
|
||||||
}
|
}
|
||||||
|
|
||||||
// findSymbol searches for the provided symbol in the given pathname to an
|
// findSymbolInELF searches for the provided symbol in the given pathname to an
|
||||||
// ELF-file. If found, it returns the offset of the symbol in the virtual
|
// ELF-file. If found, it returns the offset of the symbol in the virtual
|
||||||
// memory according to the fomula:
|
// memory according to the fomula:
|
||||||
//
|
//
|
||||||
// vmOffset = alignedOffset(section) + offsetInSection(symbol)
|
// vmOffset = alignedOffset(section) + offsetInSection(symbol)
|
||||||
//
|
//
|
||||||
// The result will be cached so that subsequent calls to findSymbol with the
|
// The result will be cached so that subsequent calls to findSymbolInELF with
|
||||||
// same pathname can quickly return.
|
// the same pathname can quickly return.
|
||||||
func (S *Scanner) findSymbol(pathname string) (offset uint64, found bool) {
|
func (S *Scanner) findSymbolInELF(pathname string) (offset uint64, found bool) {
|
||||||
|
|
||||||
// 0. Return the value from the cache, if found.
|
// 0. Return the value from the cache, if found.
|
||||||
|
S.RLock()
|
||||||
if offset, found = S.cache[pathname]; found {
|
if offset, found = S.cache[pathname]; found {
|
||||||
|
S.RUnlock()
|
||||||
return offset, found
|
return offset, found
|
||||||
}
|
}
|
||||||
|
S.RUnlock()
|
||||||
|
|
||||||
// 1. Open the file with the ELF-parser
|
// 1. Open the file with the ELF-parser
|
||||||
file, err := elf.Open(pathname)
|
file, err := elf.Open(pathname)
|
||||||
@ -308,7 +393,9 @@ func (S *Scanner) findSymbol(pathname string) (offset uint64, found bool) {
|
|||||||
|
|
||||||
// 6. Store this calculation in our cache so that we don't to touch
|
// 6. Store this calculation in our cache so that we don't to touch
|
||||||
// this file again.
|
// this file again.
|
||||||
|
S.Lock()
|
||||||
S.cache[pathname] = vmOffset
|
S.cache[pathname] = vmOffset
|
||||||
|
S.Unlock()
|
||||||
|
|
||||||
return vmOffset, true
|
return vmOffset, true
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user