{"record":{"id":"47a6a7ae13393c77","repo":"go-delve/delve","slug":"short-read-47a6a7","errorCode":null,"errorMessage":"short read","messagePattern":"short read","errorType":"exception","errorClass":"ErrShortRead","httpStatus":null,"severity":"error","filePath":"pkg/proc/native/threads_windows.go","lineNumber":125,"sourceCode":"\tif ok, err := t.dbp.Valid(); !ok {\n\t\treturn 0, err\n\t}\n\tif len(data) == 0 {\n\t\treturn 0, nil\n\t}\n\tvar count uintptr\n\terr := _WriteProcessMemory(t.dbp.os.hProcess, uintptr(addr), &data[0], uintptr(len(data)), &count)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\t// On ARM64 the instruction and data caches are not coherent,\n\t// WriteProcessMemory only updates the data cache so we must flush\n\t// the instruction cache explicitly. On x86/amd64 this is a no-op.\n\t_ = _FlushInstructionCache(t.dbp.os.hProcess, uintptr(addr), uintptr(len(data)))\n\treturn int(count), nil\n}\n\nvar ErrShortRead = errors.New(\"short read\")\n\nfunc (t *nativeThread) ReadMemory(buf []byte, addr uint64) (int, error) {\n\tif ok, err := t.dbp.Valid(); !ok {\n\t\treturn 0, err\n\t}\n\tif len(buf) == 0 {\n\t\treturn 0, nil\n\t}\n\tvar count uintptr\n\terr := _ReadProcessMemory(t.dbp.os.hProcess, uintptr(addr), &buf[0], uintptr(len(buf)), &count)\n\tif err == nil && count != uintptr(len(buf)) {\n\t\terr = ErrShortRead\n\t}\n\treturn int(count), err\n}\n\n// SoftExc returns true if this thread received a software exception during the last resume.\nfunc (t *nativeThread) SoftExc() bool {","sourceCodeStart":107,"sourceCodeEnd":143,"githubUrl":"https://github.com/go-delve/delve/blob/a23773e6c31361e43246bc43a424ee009679b174/pkg/proc/native/threads_windows.go#L107-L143","documentation":"ErrShortRead is returned by the Windows native backend's ReadMemory when ReadProcessMemory succeeds but copies fewer bytes than the requested buffer size. Delve treats a partial read as an error because variable and instruction reads need the full requested span to be valid.","triggerScenarios":"Any ReadMemory call on a Windows thread whose target address range crosses into unmapped or inaccessible memory, so ReadProcessMemory fills only part of buf and returns a short count.","commonSituations":"Reading past the end of a heap allocation or stack page, reading corrupted pointer values that point outside the process's mapped memory, or inspecting a target whose memory layout changed (process dying, guard pages).","solutions":["Check the pointer/address value being read for corruption before dereferencing it","Reduce the requested read size or read page-by-page, stopping at the first failure","Verify the target process is alive and the debug session is valid (dbp.Valid)","Attach with sufficient privileges (SeDebugPrivilege) so all relevant pages are readable"],"exampleFix":"// before\nn, err := thread.ReadMemory(buf, badPtr)\n// after\nn, err := thread.ReadMemory(buf, badPtr)\nif err == proc.ErrShortRead {\n    buf = buf[:n] // use only the bytes actually read\n}","handlingStrategy":"try-catch","validationCode":"// pre-check: is the debugged process still valid?\n// Delve checks internally via dbp.Valid(); from the RPC layer check process state first.\nif !attached || processExited {\n    return fmt.Errorf(\"target not available\")\n}","typeGuard":"func isShortRead(err error) bool {\n    return errors.Is(err, proc.ErrShortRead)\n}","tryCatchPattern":"n, err := thread.ReadMemory(buf, addr)\nif errors.Is(err, proc.ErrShortRead) {\n    // use only the bytes that were read: buf[:n]\n    handlePartial(buf[:n])\n    return nil\n} else if err != nil {\n    return err\n}","preventionTips":["Validate pointers before reading (they may be garbage after corruption)","Read in page-sized chunks and stop at the first failure","Check target process liveness before memory operations"],"tags":["windows","memory-read","native-backend"],"backgroundTag":"short-memory-read","analyzedSha":"a23773e6c31361e43246bc43a424ee009679b174","analyzedAt":"2026-08-31T15:12:45.221Z","schemaVersion":2},"datasetVersion":"2026-08-31T22:30:34.772Z"}