Add /proc/[pid]/maps to runsc trace procfs

Updates #7897

PiperOrigin-RevId: 478903608
This commit is contained in:
Shambhavi Srivastava
2022-10-04 15:57:45 -07:00
committed by gVisor bot
parent 631477c7b8
commit 30182dc10f
7 changed files with 93 additions and 29 deletions
+7 -6
View File
@@ -117,10 +117,11 @@ func (a AccessType) Effective() AccessType {
// Convenient access types.
var (
NoAccess = AccessType{}
Read = AccessType{Read: true}
Write = AccessType{Write: true}
Execute = AccessType{Execute: true}
ReadWrite = AccessType{Read: true, Write: true}
AnyAccess = AccessType{Read: true, Write: true, Execute: true}
NoAccess = AccessType{}
Read = AccessType{Read: true}
Write = AccessType{Write: true}
Execute = AccessType{Execute: true}
ReadWrite = AccessType{Read: true, Write: true}
ReadExecute = AccessType{Read: true, Execute: true}
AnyAccess = AccessType{Read: true, Write: true, Execute: true}
)
+1 -1
View File
@@ -544,7 +544,7 @@ var _ dynamicInode = (*mapsData)(nil)
// Generate implements vfs.DynamicBytesSource.Generate.
func (d *mapsData) Generate(ctx context.Context, buf *bytes.Buffer) error {
if mm := getMM(d.task); mm != nil {
mm.ReadMapsDataInto(ctx, buf)
mm.ReadMapsDataInto(ctx, mm.MapsCallbackFuncForBuffer(buf))
}
return nil
}
+3
View File
@@ -50,6 +50,9 @@ import (
"gvisor.dev/gvisor/pkg/sentry/platform"
)
// MapsCallbackFunc has all the parameters required for populating an entry of /proc/[pid]/maps.
type MapsCallbackFunc func(start, end hostarch.Addr, permissions hostarch.AccessType, private string, offset uint64, devMajor, devMinor uint32, inode uint64, path string)
// MemoryManager implements a virtual address space.
//
// +stateify savable
+34 -22
View File
@@ -20,6 +20,7 @@ import (
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/hostarch"
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/sentry/fs/proc/seqfile"
"gvisor.dev/gvisor/pkg/sentry/memmap"
)
@@ -57,9 +58,32 @@ func (mm *MemoryManager) NeedsUpdate(generation int64) bool {
return true
}
// MapsCallbackFuncForBuffer creates a /proc/[pid]/maps entry including the trailing newline.
func (mm *MemoryManager) MapsCallbackFuncForBuffer(buf *bytes.Buffer) MapsCallbackFunc {
return func(start, end hostarch.Addr, permissions hostarch.AccessType, private string, offset uint64, devMajor, devMinor uint32, inode uint64, path string) {
// Do not include the guard page: fs/proc/task_mmu.c:show_map_vma() =>
// stack_guard_page_start().
lineLen, err := fmt.Fprintf(buf, "%08x-%08x %s%s %08x %02x:%02x %d ",
start, end, permissions, private, offset, devMajor, devMinor, inode)
if err != nil {
log.Warningf("Failed to write to buffer with error: %v", err)
return
}
if path != "" {
// Per linux, we pad until the 74th character.
for pad := 73 - lineLen; pad > 0; pad-- {
buf.WriteByte(' ') // never returns a non-nil error
}
buf.WriteString(path) // never returns a non-nil error
}
buf.WriteByte('\n') // never returns a non-nil error
}
}
// ReadMapsDataInto is called by fsimpl/proc.mapsData.Generate to
// implement /proc/[pid]/maps.
func (mm *MemoryManager) ReadMapsDataInto(ctx context.Context, buf *bytes.Buffer) {
func (mm *MemoryManager) ReadMapsDataInto(ctx context.Context, fn MapsCallbackFunc) {
// FIXME(b/235153601): Need to replace RLockBypass with RLockBypass
// after fixing b/235153601.
mm.mappingMu.RLockBypass()
@@ -67,7 +91,7 @@ func (mm *MemoryManager) ReadMapsDataInto(ctx context.Context, buf *bytes.Buffer
var start hostarch.Addr
for vseg := mm.vmas.LowerBoundSegment(start); vseg.Ok(); vseg = vseg.NextSegment() {
mm.appendVMAMapsEntryLocked(ctx, vseg, buf)
mm.appendVMAMapsEntryLocked(ctx, vseg, fn)
}
// We always emulate vsyscall, so advertise it here. Everything about a
@@ -80,7 +104,7 @@ func (mm *MemoryManager) ReadMapsDataInto(ctx context.Context, buf *bytes.Buffer
//
// Artifically adjust the seqfile handle so we only output vsyscall entry once.
if start != vsyscallEnd {
buf.WriteString(vsyscallMapsEntry)
fn(hostarch.Addr(0xffffffffff600000), hostarch.Addr(0xffffffffff601000), hostarch.ReadExecute, "p", 0, 0, 0, 0, "[vsyscall]")
}
}
@@ -129,12 +153,12 @@ func (mm *MemoryManager) ReadMapsSeqFileData(ctx context.Context, handle seqfile
// Preconditions: mm.mappingMu must be locked.
func (mm *MemoryManager) vmaMapsEntryLocked(ctx context.Context, vseg vmaIterator) []byte {
var b bytes.Buffer
mm.appendVMAMapsEntryLocked(ctx, vseg, &b)
mm.appendVMAMapsEntryLocked(ctx, vseg, mm.MapsCallbackFuncForBuffer(&b))
return b.Bytes()
}
// Preconditions: mm.mappingMu must be locked.
func (mm *MemoryManager) appendVMAMapsEntryLocked(ctx context.Context, vseg vmaIterator, b *bytes.Buffer) {
func (mm *MemoryManager) appendVMAMapsEntryLocked(ctx context.Context, vseg vmaIterator, fn MapsCallbackFunc) {
vma := vseg.ValuePtr()
private := "p"
if !vma.private {
@@ -149,31 +173,19 @@ func (mm *MemoryManager) appendVMAMapsEntryLocked(ctx context.Context, vseg vmaI
devMajor := uint32(dev >> devMinorBits)
devMinor := uint32(dev & ((1 << devMinorBits) - 1))
// Do not include the guard page: fs/proc/task_mmu.c:show_map_vma() =>
// stack_guard_page_start().
lineLen, _ := fmt.Fprintf(b, "%08x-%08x %s%s %08x %02x:%02x %d ",
vseg.Start(), vseg.End(), vma.realPerms, private, vma.off, devMajor, devMinor, ino)
// Figure out our filename or hint.
var s string
var path string
if vma.hint != "" {
s = vma.hint
path = vma.hint
} else if vma.id != nil {
// FIXME(jamieliu): We are holding mm.mappingMu here, which is
// consistent with Linux's holding mmap_sem in
// fs/proc/task_mmu.c:show_map_vma() => fs/seq_file.c:seq_file_path().
// However, it's not clear that fs.File.MappedName() is actually
// consistent with this lock order.
s = vma.id.MappedName(ctx)
path = vma.id.MappedName(ctx)
}
if s != "" {
// Per linux, we pad until the 74th character.
for pad := 73 - lineLen; pad > 0; pad-- {
b.WriteByte(' ')
}
b.WriteString(s)
}
b.WriteByte('\n')
fn(vseg.Start(), vseg.End(), vma.realPerms, private, vma.off, devMajor, devMinor, ino, path)
}
// ReadSmapsDataInto is called by fsimpl/proc.smapsData.Generate to
@@ -239,7 +251,7 @@ func (mm *MemoryManager) vmaSmapsEntryLocked(ctx context.Context, vseg vmaIterat
}
func (mm *MemoryManager) vmaSmapsEntryIntoLocked(ctx context.Context, vseg vmaIterator, b *bytes.Buffer) {
mm.appendVMAMapsEntryLocked(ctx, vseg, b)
mm.appendVMAMapsEntryLocked(ctx, vseg, mm.MapsCallbackFuncForBuffer(b))
vma := vseg.ValuePtr()
// We take mm.activeMu here in each call to vmaSmapsEntryLocked, instead of
+1
View File
@@ -9,6 +9,7 @@ go_library(
deps = [
"//pkg/abi/linux",
"//pkg/context",
"//pkg/hostarch",
"//pkg/log",
"//pkg/sentry/fsimpl/proc",
"//pkg/sentry/kernel",
+37
View File
@@ -23,6 +23,7 @@ import (
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/hostarch"
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/sentry/fsimpl/proc"
"gvisor.dev/gvisor/pkg/sentry/kernel"
@@ -65,6 +66,18 @@ type Stat struct {
SID int32 `json:"sid"`
}
// Mapping contains information for /proc/[pid]/maps.
type Mapping struct {
Address hostarch.AddrRange `json:"address,omitempty"`
Permissions hostarch.AccessType `json:"permissions"`
Private string `json:"private,omitempty"`
Offset uint64 `json:"offset"`
DevMajor uint32 `json:"deviceMajor,omitempty"`
DevMinor uint32 `json:"deviceMinor,omitempty"`
Inode uint64 `json:"inode,omitempty"`
Pathname string `json:"pathname,omitempty"`
}
// ProcessProcfsDump contains the procfs dump for one process. For more details
// on fields that directly correspond to /proc fields, see proc(5).
type ProcessProcfsDump struct {
@@ -92,6 +105,8 @@ type ProcessProcfsDump struct {
Status Status `json:"status,omitempty"`
// Stat is /proc/[pid]/stat.
Stat Stat `json:"stat,omitempty"`
// Maps is /proc/[pid]/maps.
Maps []Mapping `json:"maps,omitempty"`
}
// getMM returns t's MemoryManager. On success, the MemoryManager's users count
@@ -251,6 +266,27 @@ func getStat(t *kernel.Task, pid kernel.ThreadID, pidns *kernel.PIDNamespace) St
}
}
func getMappings(ctx context.Context, mm *mm.MemoryManager) []Mapping {
var maps []Mapping
mm.ReadMapsDataInto(ctx, func(start, end hostarch.Addr, permissions hostarch.AccessType, private string, offset uint64, devMajor, devMinor uint32, inode uint64, path string) {
maps = append(maps, Mapping{
Address: hostarch.AddrRange{
Start: start,
End: end,
},
Permissions: permissions,
Private: private,
Offset: offset,
DevMajor: devMajor,
DevMinor: devMinor,
Inode: inode,
Pathname: path,
})
})
return maps
}
// Dump returns a procfs dump for process pid. t must be a task in process pid.
func Dump(t *kernel.Task, pid kernel.ThreadID, pidns *kernel.PIDNamespace) (ProcessProcfsDump, error) {
ctx := t.AsyncContext()
@@ -282,5 +318,6 @@ func Dump(t *kernel.Task, pid kernel.ThreadID, pidns *kernel.PIDNamespace) (Proc
Cgroup: t.GetCgroupEntries(),
Status: getStatus(t, mm, pid, pidns),
Stat: getStat(t, pid, pidns),
Maps: getMappings(ctx, mm),
}, nil
}
+10
View File
@@ -439,4 +439,14 @@ func TestProcfsDump(t *testing.T) {
if procfsDump[0].Status.VMRSS == 0 {
t.Errorf("expected VMSize to be set")
}
if len(procfsDump[0].Maps) <= 0 {
t.Errorf("no region mapped for pid:%v", procfsDump[0].Status.PID)
}
maps := procfsDump[0].Maps
for i := 0; i < len(procfsDump[0].Maps)-1; i++ {
if maps[i].Address.Overlaps(maps[i+1].Address) {
t.Errorf("overlapped addresses for pid:%v", procfsDump[0].Status.PID)
}
}
}