-
Notifications
You must be signed in to change notification settings - Fork 95
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(ksymbols): reintroduce lazy symbol queries #437
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -29,13 +29,9 @@ func symNotFoundErr(v interface{}) error { | |
// Interface implementation | ||
// | ||
|
||
type name struct { | ||
name string | ||
} | ||
type name string | ||
|
||
type addr struct { | ||
addr uint64 | ||
} | ||
type addr uint64 | ||
|
||
type nameAndOwner struct { | ||
name string | ||
|
@@ -48,41 +44,79 @@ type addrAndOwner struct { | |
} | ||
|
||
type KernelSymbolTable struct { | ||
symbols map[name][]*KernelSymbol | ||
addrs map[addr][]*KernelSymbol | ||
symByName map[nameAndOwner][]*KernelSymbol | ||
symByAddr map[addrAndOwner][]*KernelSymbol | ||
textSegStart uint64 | ||
textSegEnd uint64 | ||
updateLock *sync.RWMutex | ||
updateWg *sync.WaitGroup | ||
symbols map[name][]*KernelSymbol | ||
addrs map[addr][]*KernelSymbol | ||
requiredSyms map[string]struct{} | ||
requiredAddrs map[uint64]struct{} | ||
onlyRequired bool | ||
symByName map[nameAndOwner][]*KernelSymbol | ||
symByAddr map[addrAndOwner][]*KernelSymbol | ||
updateLock *sync.Mutex | ||
updateWg *sync.WaitGroup | ||
} | ||
|
||
func NewKernelSymbolTable() (*KernelSymbolTable, error) { | ||
func NewKernelSymbolTable(opts ...KSymbTableOption) (*KernelSymbolTable, error) { | ||
k := KernelSymbolTable{ | ||
updateLock: &sync.RWMutex{}, | ||
updateLock: &sync.Mutex{}, | ||
} | ||
for _, opt := range opts { | ||
err := opt(&k) | ||
if err != nil { | ||
return nil, err | ||
} | ||
} | ||
|
||
k.onlyRequired = k.requiredAddrs != nil || k.requiredSyms != nil | ||
|
||
return &k, k.Refresh() | ||
} | ||
|
||
type KSymbTableOption func(k *KernelSymbolTable) error | ||
|
||
func WithRequiredSymbols(reqSyms []string) KSymbTableOption { | ||
return func(k *KernelSymbolTable) error { | ||
k.requiredSyms = sliceToValidationMap(reqSyms) | ||
return nil | ||
} | ||
} | ||
|
||
func WithRequiredAddresses(reqAddrs []uint64) KSymbTableOption { | ||
return func(k *KernelSymbolTable) error { | ||
k.requiredAddrs = sliceToValidationMap(reqAddrs) | ||
return nil | ||
} | ||
} | ||
|
||
// | ||
// Getters (return a copy of the symbol for thread safety). | ||
// | ||
|
||
// TextSegmentContains returns true if the given address is in the kernel text segment. | ||
func (k *KernelSymbolTable) TextSegmentContains(addr uint64) (bool, error) { | ||
k.updateLock.RLock() | ||
defer k.updateLock.RUnlock() | ||
k.updateLock.Lock() | ||
defer k.updateLock.Unlock() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As the goal is to squeeze as much as possible (optimise wise), what do you think unlocking right after the |
||
|
||
segStart, segEnd, err := k.getTextSegmentAddresses() | ||
if err != nil { | ||
return false, err | ||
} | ||
|
||
return addr >= k.textSegStart && addr < k.textSegEnd, nil | ||
return addr >= segStart && addr < segEnd, nil | ||
} | ||
|
||
// GetSymbolByName returns all the symbols with the given name. | ||
// NOTE: If table is in required only mode, method will add the new symbol | ||
// and rescan the file. | ||
func (k *KernelSymbolTable) GetSymbolByName(n string) ([]KernelSymbol, error) { | ||
k.updateLock.RLock() | ||
defer k.updateLock.RUnlock() | ||
k.updateLock.Lock() | ||
defer k.updateLock.Unlock() | ||
|
||
symbols, exist := k.symbols[name{n}] | ||
err := k.validateOrAddRequiredSym(name(n)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What about returning |
||
if err != nil { | ||
return []KernelSymbol{}, nil | ||
} | ||
|
||
symbols, exist := k.symbols[name(n)] | ||
if !exist { | ||
return []KernelSymbol{}, symNotFoundErr(n) | ||
} | ||
|
@@ -91,11 +125,18 @@ func (k *KernelSymbolTable) GetSymbolByName(n string) ([]KernelSymbol, error) { | |
} | ||
|
||
// GetSymbolByAddr returns all the symbols with the given address. | ||
// NOTE: If table is in required only mode, method will add the new symbol | ||
// and rescan the file. | ||
func (k *KernelSymbolTable) GetSymbolByAddr(a uint64) ([]KernelSymbol, error) { | ||
k.updateLock.RLock() | ||
defer k.updateLock.RUnlock() | ||
k.updateLock.Lock() | ||
defer k.updateLock.Unlock() | ||
|
||
err := k.validateOrAddRequiredAddr(addr(a)) | ||
if err != nil { | ||
return []KernelSymbol{}, err | ||
} | ||
|
||
symbols, exist := k.addrs[addr{a}] | ||
symbols, exist := k.addrs[addr(a)] | ||
if !exist { | ||
return []KernelSymbol{}, symNotFoundErr(a) | ||
} | ||
|
@@ -105,8 +146,13 @@ func (k *KernelSymbolTable) GetSymbolByAddr(a uint64) ([]KernelSymbol, error) { | |
|
||
// GetSymbolByOwnerAndName returns all the symbols with the given owner and name. | ||
func (k *KernelSymbolTable) GetSymbolByOwnerAndName(o, n string) ([]KernelSymbol, error) { | ||
k.updateLock.RLock() | ||
defer k.updateLock.RUnlock() | ||
k.updateLock.Lock() | ||
defer k.updateLock.Unlock() | ||
|
||
err := k.validateOrAddRequiredSym(name(n)) | ||
if err != nil { | ||
return []KernelSymbol{}, err | ||
} | ||
|
||
symbols, exist := k.symByName[nameAndOwner{n, o}] | ||
if !exist { | ||
|
@@ -118,8 +164,13 @@ func (k *KernelSymbolTable) GetSymbolByOwnerAndName(o, n string) ([]KernelSymbol | |
|
||
// GetSymbolByOwnerAndAddr returns all the symbols with the given owner and address. | ||
func (k *KernelSymbolTable) GetSymbolByOwnerAndAddr(o string, a uint64) ([]KernelSymbol, error) { | ||
k.updateLock.RLock() | ||
defer k.updateLock.RUnlock() | ||
k.updateLock.Lock() | ||
defer k.updateLock.Unlock() | ||
|
||
err := k.validateOrAddRequiredAddr(addr(a)) | ||
if err != nil { | ||
return []KernelSymbol{}, err | ||
} | ||
|
||
symbols, exist := k.symByAddr[addrAndOwner{a, o}] | ||
if !exist { | ||
|
@@ -144,8 +195,11 @@ func (k *KernelSymbolTable) GetSymbolByOwnerAndAddr(o string, a uint64) ([]Kerne | |
|
||
// Refresh refreshes the KernelSymbolTable, reading the symbols from /proc/kallsyms. | ||
func (k *KernelSymbolTable) Refresh() error { | ||
k.updateLock.Lock() | ||
defer k.updateLock.Unlock() | ||
// Refresh may be called internally, after lock was already takne | ||
needUnlock := k.updateLock.TryLock() | ||
if needUnlock { | ||
defer k.updateLock.Unlock() | ||
} | ||
|
||
// re-initialize the maps to include all new symbols. | ||
k.symbols = make(map[name][]*KernelSymbol) | ||
|
@@ -182,25 +236,54 @@ func (k *KernelSymbolTable) Refresh() error { | |
// Finally, wait for the map update goroutines to finish. | ||
k.updateWg.Wait() | ||
|
||
// Get the kernel text segment addresses. | ||
return k.getTextSegmentAddresses() | ||
return nil | ||
} | ||
|
||
// | ||
// Private methods. | ||
// | ||
|
||
// getTextSegmentAddresses gets the start and end addresses of the kernel text segment. | ||
func (k *KernelSymbolTable) getTextSegmentAddresses() error { | ||
func (k *KernelSymbolTable) getTextSegmentAddresses() (uint64, uint64, error) { | ||
stext, exist1 := k.symByName[nameAndOwner{"_stext", "system"}] | ||
etext, exist2 := k.symByName[nameAndOwner{"_etext", "system"}] | ||
|
||
if !exist1 || !exist2 { | ||
return fmt.Errorf("kernel text segment symbol(s) not found") | ||
return 0, 0, fmt.Errorf("kernel text segment symbol(s) not found") | ||
} | ||
|
||
k.textSegStart = stext[0].Address | ||
k.textSegEnd = etext[0].Address | ||
textSegStart := stext[0].Address | ||
textSegEnd := etext[0].Address | ||
|
||
return textSegStart, textSegEnd, nil | ||
} | ||
|
||
// validateOrAddRequiredSym checks if the given symbol is in the required list. | ||
// If not, it adds it and rescans the kernel symbols. | ||
func (k *KernelSymbolTable) validateOrAddRequiredSym(sym name) error { | ||
if k.onlyRequired && k.requiredSyms != nil { | ||
sym := string(sym) | ||
if _, ok := k.requiredSyms[sym]; !ok { | ||
k.requiredSyms[sym] = struct{}{} | ||
err := k.Refresh() | ||
return err | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// validateOrAddRequiredAddr checks if the given address is in the required list. | ||
// If not, it adds it and rescans the kernel symbols. | ||
func (k *KernelSymbolTable) validateOrAddRequiredAddr(addr addr) error { | ||
if k.onlyRequired && k.requiredAddrs != nil { | ||
addr := uint64(addr) | ||
if _, ok := k.requiredAddrs[addr]; !ok { | ||
k.requiredAddrs[addr] = struct{}{} | ||
err := k.Refresh() | ||
return err | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
@@ -225,6 +308,13 @@ func (k *KernelSymbolTable) processLines(chans []chan *KernelSymbol) error { | |
continue | ||
} | ||
if sym := parseLine(fields); sym != nil { | ||
if k.onlyRequired { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can't we assume at this stage that if |
||
_, symRequired := k.requiredSyms[sym.Name] | ||
_, addrRequired := k.requiredAddrs[sym.Address] | ||
if !symRequired && !addrRequired { | ||
continue | ||
} | ||
} | ||
for _, ch := range chans { | ||
ch <- sym | ||
} | ||
|
@@ -244,7 +334,7 @@ func (k *KernelSymbolTable) updateSymbolMap(symbolChan chan *KernelSymbol) { | |
defer k.updateWg.Done() | ||
|
||
for sym := range symbolChan { | ||
key := name{sym.Name} | ||
key := name(sym.Name) | ||
k.symbols[key] = append(k.symbols[key], sym) | ||
} | ||
} | ||
|
@@ -254,7 +344,7 @@ func (k *KernelSymbolTable) updateAddrsMap(addrChan chan *KernelSymbol) { | |
defer k.updateWg.Done() | ||
|
||
for sym := range addrChan { | ||
key := addr{sym.Address} | ||
key := addr(sym.Address) | ||
k.addrs[key] = append(k.addrs[key], sym) | ||
} | ||
} | ||
|
@@ -315,3 +405,11 @@ func copySliceOfPointersToSliceOfStructs(s []*KernelSymbol) []KernelSymbol { | |
} | ||
return ret | ||
} | ||
|
||
func sliceToValidationMap[T comparable](items []T) map[T]struct{} { | ||
res := make(map[T]struct{}) | ||
for _, item := range items { | ||
res[item] = struct{}{} | ||
} | ||
return res | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good call.