proc/*: remove proc.Thread.Blocked, refactor memory access (#2206)

On linux we can not read memory if the thread we use to do it is
occupied doing certain system calls. The exact conditions when this
happens have never been clear.

This problem was worked around by using the Blocked method which
recognized the most common circumstances where this would happen.

However this is a hack: Blocked returning true doesn't mean that the
problem will manifest and Blocked returning false doesn't necessarily
mean the problem will not manifest. A side effect of this is issue
#2151 where sometimes we can't read the memory of a thread and find its
associated goroutine.

This commit fixes this problem by always reading memory using a thread
we know to be good for this, specifically the one returned by
ContinueOnce. In particular the changes are as follows:

1. Remove (ProcessInternal).CurrentThread and
(ProcessInternal).SetCurrentThread, the "current thread" becomes a
field of Target, CurrentThread becomes a (*Target) method and
(*Target).SwitchThread basically just sets a field Target.

2. The backends keep track of their own internal idea of what the
current thread is, to use it to read memory, this is the thread they
return from ContinueOnce as trapthread

3. The current thread in the backend and the current thread in Target
only ever get synchronized in two places: when the backend creates a
Target object the currentThread field of Target is initialized with the
backend's current thread and when (*Target).Restart gets called (when a
recording is rewound the currentThread used by Target might not exist
anymore).

4. We remove the MemoryReadWriter interface embedded in Thread and
instead add a Memory method to Process that returns a MemoryReadWriter.
The  backends will return something here that will read memory using
the current thread saved by the backend.

5. The Thread.Blocked method is removed

One possible problem with this change is processes that have threads
with different memory maps. As far as I can determine this could happen
on old versions of linux but this option was removed in linux 2.5.

Fixes #2151
This commit is contained in:
Alessandro Arzilli
2020-11-09 20:28:40 +01:00
committed by GitHub
parent e69d536e81
commit 0843376018
33 changed files with 181 additions and 348 deletions

View File

@ -42,7 +42,8 @@ const (
const elfErrorBadMagicNumber = "bad magic number"
func linuxThreadsFromNotes(p *process, notes []*note, machineType elf.Machine) {
func linuxThreadsFromNotes(p *process, notes []*note, machineType elf.Machine) proc.Thread {
var currentThread proc.Thread
var lastThreadAMD *linuxAMD64Thread
var lastThreadARM *linuxARM64Thread
for _, note := range notes {
@ -52,15 +53,15 @@ func linuxThreadsFromNotes(p *process, notes []*note, machineType elf.Machine) {
t := note.Desc.(*linuxPrStatusAMD64)
lastThreadAMD = &linuxAMD64Thread{linutil.AMD64Registers{Regs: &t.Reg}, t}
p.Threads[int(t.Pid)] = &thread{lastThreadAMD, p, proc.CommonThread{}}
if p.currentThread == nil {
p.currentThread = p.Threads[int(t.Pid)]
if currentThread == nil {
currentThread = p.Threads[int(t.Pid)]
}
} else if machineType == _EM_AARCH64 {
t := note.Desc.(*linuxPrStatusARM64)
lastThreadARM = &linuxARM64Thread{linutil.ARM64Registers{Regs: &t.Reg}, t}
p.Threads[int(t.Pid)] = &thread{lastThreadARM, p, proc.CommonThread{}}
if p.currentThread == nil {
p.currentThread = p.Threads[int(t.Pid)]
if currentThread == nil {
currentThread = p.Threads[int(t.Pid)]
}
}
case _NT_FPREGSET:
@ -79,6 +80,7 @@ func linuxThreadsFromNotes(p *process, notes []*note, machineType elf.Machine) {
p.pid = int(note.Desc.(*linuxPrPsInfo).Pid)
}
}
return currentThread
}
// readLinuxCore reads a core file from corePath corresponding to the executable at
@ -87,35 +89,35 @@ func linuxThreadsFromNotes(p *process, notes []*note, machineType elf.Machine) {
// http://uhlo.blogspot.fr/2012/05/brief-look-into-core-dumps.html,
// elf_core_dump in http://lxr.free-electrons.com/source/fs/binfmt_elf.c,
// and, if absolutely desperate, readelf.c from the binutils source.
func readLinuxCore(corePath, exePath string) (*process, error) {
func readLinuxCore(corePath, exePath string) (*process, proc.Thread, error) {
coreFile, err := elf.Open(corePath)
if err != nil {
if _, isfmterr := err.(*elf.FormatError); isfmterr && (strings.Contains(err.Error(), elfErrorBadMagicNumber) || strings.Contains(err.Error(), " at offset 0x0: too short")) {
// Go >=1.11 and <1.11 produce different errors when reading a non-elf file.
return nil, ErrUnrecognizedFormat
return nil, nil, ErrUnrecognizedFormat
}
return nil, err
return nil, nil, err
}
exe, err := os.Open(exePath)
if err != nil {
return nil, err
return nil, nil, err
}
exeELF, err := elf.NewFile(exe)
if err != nil {
return nil, err
return nil, nil, err
}
if coreFile.Type != elf.ET_CORE {
return nil, fmt.Errorf("%v is not a core file", coreFile)
return nil, nil, fmt.Errorf("%v is not a core file", coreFile)
}
if exeELF.Type != elf.ET_EXEC && exeELF.Type != elf.ET_DYN {
return nil, fmt.Errorf("%v is not an exe file", exeELF)
return nil, nil, fmt.Errorf("%v is not an exe file", exeELF)
}
machineType := exeELF.Machine
notes, err := readNotes(coreFile, machineType)
if err != nil {
return nil, err
return nil, nil, err
}
memory := buildMemory(coreFile, exeELF, exe, notes)
@ -127,7 +129,7 @@ func readLinuxCore(corePath, exePath string) (*process, error) {
case _EM_AARCH64:
bi = proc.NewBinaryInfo("linux", "arm64")
default:
return nil, fmt.Errorf("unsupported machine type")
return nil, nil, fmt.Errorf("unsupported machine type")
}
entryPoint := findEntryPoint(notes, bi.Arch.PtrSize())
@ -140,8 +142,8 @@ func readLinuxCore(corePath, exePath string) (*process, error) {
breakpoints: proc.NewBreakpointMap(),
}
linuxThreadsFromNotes(p, notes, machineType)
return p, nil
currentThread := linuxThreadsFromNotes(p, notes, machineType)
return p, currentThread, nil
}
type linuxAMD64Thread struct {