| // Copyright 2017 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package core |
| |
| import ( |
| "fmt" |
| "os" |
| "strings" |
| ) |
| |
| // A Mapping represents a contiguous subset of the inferior's address space. |
| type Mapping struct { |
| min Address |
| max Address |
| perm Perm |
| |
| f *os.File // file backing this region |
| off int64 // offset of start of this mapping in f |
| |
| // For regions originally backed by a file but now in the core file, |
| // (probably because it is copy-on-write) this is the original data source. |
| // This info is just for printing; the data in this source is stale. |
| origF *os.File |
| origOff int64 |
| |
| // Contents of f at offset off. Length=max-min. |
| contents []byte |
| } |
| |
| // Min returns the lowest virtual address of the mapping. |
| func (m *Mapping) Min() Address { |
| return m.min |
| } |
| |
| // Max returns the virtual address of the byte just beyond the mapping. |
| func (m *Mapping) Max() Address { |
| return m.max |
| } |
| |
| // Size returns int64(Max-Min) |
| func (m *Mapping) Size() int64 { |
| return m.max.Sub(m.min) |
| } |
| |
| // Perm returns the permissions on the mapping. |
| func (m *Mapping) Perm() Perm { |
| return m.perm |
| } |
| |
| // Source returns the backing file and offset for the mapping, or "", 0 if none. |
| func (m *Mapping) Source() (string, int64) { |
| if m.f == nil { |
| return "", 0 |
| } |
| return m.f.Name(), m.off |
| } |
| |
| // CopyOnWrite reports whether the mapping is a copy-on-write region, i.e. |
| // it started as a mapped file and is now writeable. |
| // TODO: is this distinguishable from a write-back region? |
| func (m *Mapping) CopyOnWrite() bool { |
| return m.origF != nil |
| } |
| |
| // For CopyOnWrite mappings, OrigSource returns the file/offset of the |
| // original copy of the data, or "", 0 if none. |
| func (m *Mapping) OrigSource() (string, int64) { |
| if m.origF == nil { |
| return "", 0 |
| } |
| return m.origF.Name(), m.origOff |
| } |
| |
| // A Perm represents the permissions allowed for a Mapping. |
| type Perm uint8 |
| |
| const ( |
| Read Perm = 1 << iota |
| Write |
| Exec |
| ) |
| |
| func (p Perm) String() string { |
| var a [3]string |
| b := a[:0] |
| if p&Read != 0 { |
| b = append(b, "Read") |
| } |
| if p&Write != 0 { |
| b = append(b, "Write") |
| } |
| if p&Exec != 0 { |
| b = append(b, "Exec") |
| } |
| if len(b) == 0 { |
| b = append(b, "None") |
| } |
| return strings.Join(b, "|") |
| } |
| |
| // We assume that OS pages are at least 4K in size. So every mapping |
| // starts and ends at a multiple of 4K. |
| // We divide the other 64-12 = 52 bits into levels in a page table. |
| type pageTable0 [1 << 10]*Mapping |
| type pageTable1 [1 << 10]*pageTable0 |
| type pageTable2 [1 << 10]*pageTable1 |
| type pageTable3 [1 << 10]*pageTable2 |
| type pageTable4 [1 << 12]*pageTable3 |
| |
| const pageSize Address = 1 << 12 |
| |
| // findMapping is simple enough that it inlines. |
| func (p *Process) findMapping(a Address) *Mapping { |
| t3 := p.pageTable[a>>52] |
| if t3 == nil { |
| return nil |
| } |
| t2 := t3[a>>42%(1<<10)] |
| if t2 == nil { |
| return nil |
| } |
| t1 := t2[a>>32%(1<<10)] |
| if t1 == nil { |
| return nil |
| } |
| t0 := t1[a>>22%(1<<10)] |
| if t0 == nil { |
| return nil |
| } |
| return t0[a>>12%(1<<10)] |
| } |
| |
| func (p *Process) addMapping(m *Mapping) error { |
| if m.min%(pageSize) != 0 { |
| return fmt.Errorf("mapping start %x isn't a multiple of 4096", m.min) |
| } |
| if m.max%(pageSize) != 0 { |
| return fmt.Errorf("mapping end %x isn't a multiple of 4096", m.max) |
| } |
| for a := m.min; a < m.max; a += 1 << 12 { |
| i3 := a >> 52 |
| t3 := p.pageTable[i3] |
| if t3 == nil { |
| t3 = new(pageTable3) |
| p.pageTable[i3] = t3 |
| } |
| i2 := a >> 42 % (1 << 10) |
| t2 := t3[i2] |
| if t2 == nil { |
| t2 = new(pageTable2) |
| t3[i2] = t2 |
| } |
| i1 := a >> 32 % (1 << 10) |
| t1 := t2[i1] |
| if t1 == nil { |
| t1 = new(pageTable1) |
| t2[i1] = t1 |
| } |
| i0 := a >> 22 % (1 << 10) |
| t0 := t1[i0] |
| if t0 == nil { |
| t0 = new(pageTable0) |
| t1[i0] = t0 |
| } |
| t0[a>>12%(1<<10)] = m |
| } |
| return nil |
| } |
| |
| // splicedMemory represents a memory space formed from multiple regions. |
| // Much of the logic was copied from delve/pkg/proc/core.go. |
| type splicedMemory struct { |
| mappings []*Mapping |
| } |
| |
| func (s *splicedMemory) Add(min, max Address, perm Perm, f *os.File, off int64) { |
| if max-min <= 0 { |
| return |
| } |
| |
| // Align max. |
| if max%pageSize != 0 { |
| max = (max + pageSize) & ^(pageSize - 1) |
| } |
| // Align min. |
| if gap := min % pageSize; gap != 0 { |
| off -= int64(gap) |
| min -= gap |
| } |
| |
| newMappings := make([]*Mapping, 0, len(s.mappings)+1) |
| add := func(m *Mapping) { |
| if m.Size() <= 0 { |
| return |
| } |
| newMappings = append(newMappings, m) |
| } |
| |
| inserted := false |
| for _, entry := range s.mappings { |
| switch { |
| case entry.max < min: // entry is completely before the new region. |
| add(entry) |
| case max < entry.min: // entry is completely after the new region. |
| if !inserted { |
| add(&Mapping{min: min, max: max, perm: perm, f: f, off: off}) |
| inserted = true |
| } |
| add(entry) |
| case min <= entry.min && entry.max <= max: |
| // entry is completely overwritten by the new region. Drop. |
| case entry.min <= min && entry.max <= max: |
| // new region overwrites the end of the entry. |
| entry.max = min |
| add(entry) |
| case min <= entry.min && max <= entry.max: |
| // new region overwrites the begining of the entry. |
| if !inserted { |
| add(&Mapping{min: min, max: max, perm: perm, f: f, off: off}) |
| inserted = true |
| } |
| entry.off += int64(max - entry.min) |
| entry.min = max |
| add(entry) |
| case entry.min < min && max < entry.max: |
| // new region punches a hole in the entry. |
| entry2 := *entry |
| |
| entry.max = min |
| entry2.off += int64(max - entry.min) |
| entry2.min = max |
| add(entry) |
| add(&Mapping{min: min, max: max, perm: perm, f: f, off: off}) |
| add(&entry2) |
| inserted = true |
| default: |
| panic(fmt.Sprintf("Unhandled case: existing entry is (min:0x%x max:0x%x), new entry is (min:0x%x max:0x%x)", entry.min, entry.max, min, max)) |
| } |
| } |
| if !inserted { |
| add(&Mapping{min: min, max: max, perm: perm, f: f, off: off}) |
| } |
| s.mappings = newMappings |
| } |