fix(debug): handle mapping edges, stray traps and dying debuggees
Assisted-by: GLM 5.3
This commit is contained in:
+50
-27
@@ -91,6 +91,16 @@ func LaunchWithBuffers(gasmBin, asmPath, funcName string, args []byte, bufSpec s
|
||||
if _, err := os.Stat(readyFile); err == nil {
|
||||
break
|
||||
}
|
||||
// A debuggee that died before signalling readiness (unknown
|
||||
// function, unparseable source) writes its failure notice to the
|
||||
// handshake directory; read it and fail fast. The poll never
|
||||
// waits on the child: a wait here could consume the SIGSTOP park
|
||||
// that waitStopped below must receive, hanging the launch.
|
||||
if err := s.deadReason(); err != nil {
|
||||
cmd.Wait()
|
||||
os.RemoveAll(tmpDir)
|
||||
return nil, nil, err
|
||||
}
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
}
|
||||
|
||||
@@ -131,6 +141,18 @@ func LaunchWithBuffers(gasmBin, asmPath, funcName string, args []byte, bufSpec s
|
||||
return s, bufAddrs, nil
|
||||
}
|
||||
|
||||
// deadReason reports the debuggee's own failure notice, the file its
|
||||
// failure paths write before exiting. A debuggee killed without a notice
|
||||
// (a crash, SIGKILL) surfaces through waitStopped after the poll instead,
|
||||
// which is why the poll's budget stays finite.
|
||||
func (s *Session) deadReason() error {
|
||||
data, err := os.ReadFile(filepath.Join(s.tmpDir, "dead"))
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("debug: debuggee failed before signalling readiness: %s", strings.TrimSpace(string(data)))
|
||||
}
|
||||
|
||||
// waitStopped consumes ptrace-stop events until one the debugger cares
|
||||
// about arrives: SIGTRAP (a breakpoint or a completed single-step), the
|
||||
// debuggee's own SIGSTOP, or a genuine signal-delivery-stop. A Go tracee's
|
||||
@@ -219,40 +241,41 @@ func (s *Session) Poke(addr, val uint64) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReadMemory reads len bytes from the debuggee's memory at addr.
|
||||
// ReadMemory reads len bytes from the debuggee's memory at addr. The read
|
||||
// covers exactly the requested range: the old word-at-a-time loop read a
|
||||
// whole 8-byte word for the final partial word, so a request that ended
|
||||
// inside the last mapped page failed whenever the following page was
|
||||
// unmapped, even though every requested byte was readable.
|
||||
func (s *Session) ReadMemory(addr uint64, length int) ([]byte, error) {
|
||||
out := make([]byte, length)
|
||||
for i := 0; i < length; i += 8 {
|
||||
word, err := s.Peek(addr + uint64(i))
|
||||
if err != nil {
|
||||
return out[:i], err
|
||||
}
|
||||
for j := 0; j < 8 && i+j < length; j++ {
|
||||
out[i+j] = byte(word >> (8 * j))
|
||||
}
|
||||
mem, err := os.OpenFile(fmt.Sprintf("/proc/%d/mem", s.pid), os.O_RDONLY, 0)
|
||||
if err != nil {
|
||||
return out, fmt.Errorf("debug: open /proc/%d/mem: %w", s.pid, err)
|
||||
}
|
||||
defer mem.Close()
|
||||
n, err := mem.ReadAt(out, int64(addr))
|
||||
if err != nil {
|
||||
return out[:n], fmt.Errorf("debug: read mem %#x: %w", addr, err)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// WriteMemory writes bytes to the debuggee's memory at addr.
|
||||
// WriteMemory writes bytes to the debuggee's memory at addr. The write
|
||||
// covers exactly the given bytes: /proc/pid/mem accepts writes of any
|
||||
// length at any offset, so the word loop's read-modify-write of the final
|
||||
// partial word (which read past the requested range and failed on an
|
||||
// unmapped following page) is unnecessary.
|
||||
func (s *Session) WriteMemory(addr uint64, data []byte) error {
|
||||
for i := 0; i < len(data); i += 8 {
|
||||
end := min(i+8, len(data))
|
||||
var word uint64
|
||||
for j := 0; j < end-i; j++ {
|
||||
word |= uint64(data[i+j]) << (8 * j)
|
||||
}
|
||||
if end-i < 8 {
|
||||
existing, err := s.Peek(addr + uint64(i))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mask := ^((uint64(1) << (8 * (end - i))) - 1)
|
||||
word = (existing & mask) | word
|
||||
}
|
||||
if err := s.Poke(addr+uint64(i), word); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(data) == 0 {
|
||||
return nil
|
||||
}
|
||||
mem, err := os.OpenFile(fmt.Sprintf("/proc/%d/mem", s.pid), os.O_WRONLY, 0)
|
||||
if err != nil {
|
||||
return fmt.Errorf("debug: open /proc/%d/mem: %w", s.pid, err)
|
||||
}
|
||||
defer mem.Close()
|
||||
if _, err := mem.WriteAt(data, int64(addr)); err != nil {
|
||||
return fmt.Errorf("debug: write mem %#x: %w", addr, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user