github.com/undoio/delve@v1.9.0/pkg/proc/dump.go (about) 1 package proc 2 3 import ( 4 "bytes" 5 "debug/elf" 6 "encoding/binary" 7 "errors" 8 "fmt" 9 "runtime" 10 "sync" 11 12 "github.com/undoio/delve/pkg/elfwriter" 13 "github.com/undoio/delve/pkg/version" 14 ) 15 16 var ( 17 ErrMemoryMapNotSupported = errors.New("MemoryMap not supported") 18 ) 19 20 // DumpState represents the current state of a core dump in progress. 21 type DumpState struct { 22 Mutex sync.Mutex 23 24 Dumping bool 25 AllDone bool 26 Canceled bool 27 DoneChan chan struct{} 28 29 ThreadsDone, ThreadsTotal int 30 MemDone, MemTotal uint64 31 32 Err error 33 } 34 35 // DumpFlags is used to configure (*Target).Dump 36 type DumpFlags uint16 37 38 const ( 39 DumpPlatformIndependent DumpFlags = 1 << iota // always use platform-independent notes format 40 ) 41 42 // MemoryMapEntry represent a memory mapping in the target process. 43 type MemoryMapEntry struct { 44 Addr uint64 45 Size uint64 46 47 Read, Write, Exec bool 48 49 Filename string 50 Offset uint64 51 } 52 53 func (state *DumpState) setErr(err error) { 54 if err == nil { 55 return 56 } 57 state.Mutex.Lock() 58 if state.Err == nil { 59 state.Err = err 60 } 61 state.Mutex.Unlock() 62 } 63 64 func (state *DumpState) setThreadsTotal(n int) { 65 state.Mutex.Lock() 66 state.ThreadsTotal = n 67 state.ThreadsDone = 0 68 state.Mutex.Unlock() 69 } 70 71 func (state *DumpState) threadDone() { 72 state.Mutex.Lock() 73 state.ThreadsDone++ 74 state.Mutex.Unlock() 75 } 76 77 func (state *DumpState) setMemTotal(n uint64) { 78 state.Mutex.Lock() 79 state.MemTotal = n 80 state.Mutex.Unlock() 81 } 82 83 func (state *DumpState) memDone(delta uint64) { 84 state.Mutex.Lock() 85 state.MemDone += delta 86 state.Mutex.Unlock() 87 } 88 89 func (state *DumpState) isCanceled() bool { 90 state.Mutex.Lock() 91 defer state.Mutex.Unlock() 92 return state.Canceled 93 } 94 95 // Dump writes a core dump to out. State is updated as the core dump is written. 96 func (t *Target) Dump(out elfwriter.WriteCloserSeeker, flags DumpFlags, state *DumpState) { 97 defer func() { 98 state.Mutex.Lock() 99 if ierr := recover(); ierr != nil { 100 state.Err = newInternalError(ierr, 2) 101 } 102 err := out.Close() 103 if state.Err == nil && err != nil { 104 state.Err = fmt.Errorf("error writing output file: %v", err) 105 } 106 state.Dumping = false 107 state.Mutex.Unlock() 108 if state.DoneChan != nil { 109 close(state.DoneChan) 110 } 111 }() 112 113 bi := t.BinInfo() 114 115 var fhdr elf.FileHeader 116 fhdr.Class = elf.ELFCLASS64 117 fhdr.Data = elf.ELFDATA2LSB 118 fhdr.Version = elf.EV_CURRENT 119 120 switch bi.GOOS { 121 case "linux": 122 fhdr.OSABI = elf.ELFOSABI_LINUX 123 case "freebsd": 124 fhdr.OSABI = elf.ELFOSABI_FREEBSD 125 default: 126 // There is no OSABI value for windows or macOS because nobody generates ELF core dumps on those systems. 127 fhdr.OSABI = 0xff 128 } 129 130 fhdr.Type = elf.ET_CORE 131 132 switch bi.Arch.Name { 133 case "amd64": 134 fhdr.Machine = elf.EM_X86_64 135 case "386": 136 fhdr.Machine = elf.EM_386 137 case "arm64": 138 fhdr.Machine = elf.EM_AARCH64 139 default: 140 panic("not implemented") 141 } 142 143 fhdr.Entry = 0 144 145 w := elfwriter.New(out, &fhdr) 146 147 notes := []elfwriter.Note{} 148 149 entryPoint, err := t.EntryPoint() 150 if err != nil { 151 state.setErr(err) 152 return 153 } 154 155 notes = append(notes, elfwriter.Note{ 156 Type: elfwriter.DelveHeaderNoteType, 157 Name: "Delve Header", 158 Data: []byte(fmt.Sprintf("%s/%s\n%s\n%s%d\n%s%#x\n", bi.GOOS, bi.Arch.Name, version.DelveVersion.String(), elfwriter.DelveHeaderTargetPidPrefix, t.pid, elfwriter.DelveHeaderEntryPointPrefix, entryPoint)), 159 }) 160 161 threads := t.ThreadList() 162 state.setThreadsTotal(len(threads)) 163 164 var threadsDone bool 165 166 if flags&DumpPlatformIndependent == 0 { 167 threadsDone, notes, err = t.proc.DumpProcessNotes(notes, state.threadDone) 168 if err != nil { 169 state.setErr(err) 170 return 171 } 172 } 173 174 if !threadsDone { 175 for _, th := range threads { 176 if w.Err != nil { 177 state.setErr(fmt.Errorf("error writing to output file: %v", w.Err)) 178 return 179 } 180 if state.isCanceled() { 181 return 182 } 183 notes = t.dumpThreadNotes(notes, state, th) 184 state.threadDone() 185 } 186 } 187 188 memmap, err := t.proc.MemoryMap() 189 if err != nil { 190 state.setErr(err) 191 return 192 } 193 194 memmapFilter := make([]MemoryMapEntry, 0, len(memmap)) 195 memtot := uint64(0) 196 for i := range memmap { 197 mme := &memmap[i] 198 if t.shouldDumpMemory(mme) { 199 memmapFilter = append(memmapFilter, *mme) 200 memtot += mme.Size 201 } 202 } 203 204 state.setMemTotal(memtot) 205 206 for i := range memmapFilter { 207 mme := &memmapFilter[i] 208 if w.Err != nil { 209 state.setErr(fmt.Errorf("error writing to output file: %v", w.Err)) 210 return 211 } 212 if state.isCanceled() { 213 return 214 } 215 t.dumpMemory(state, w, mme) 216 } 217 218 notesProg := w.WriteNotes(notes) 219 w.Progs = append(w.Progs, notesProg) 220 w.WriteProgramHeaders() 221 if w.Err != nil { 222 state.setErr(fmt.Errorf("error writing to output file: %v", w.Err)) 223 } 224 state.Mutex.Lock() 225 state.AllDone = true 226 state.Mutex.Unlock() 227 } 228 229 // dumpThreadNotes appends notes describing a thread (thread id and its 230 // registers) using a platform-independent format. 231 func (t *Target) dumpThreadNotes(notes []elfwriter.Note, state *DumpState, th Thread) []elfwriter.Note { 232 // If the backend doesn't provide a way to dump a thread we use a custom format for the note: 233 // - thread_id (8 bytes) 234 // - pc value (8 bytes) 235 // - sp value (8 bytes) 236 // - bp value (8 bytes) 237 // - tls value (8 bytes) 238 // - has_gaddr (1 byte) 239 // - gaddr value (8 bytes) 240 // - num_registers (4 bytes) 241 // Followed by a list of num_register, each as follows: 242 // - register_name_len (2 bytes) 243 // - register_name (register_name_len bytes) 244 // - register_data_len (2 bytes) 245 // - register_data (regiter_data_len bytes) 246 247 buf := new(bytes.Buffer) 248 _ = binary.Write(buf, binary.LittleEndian, uint64(th.ThreadID())) 249 250 regs, err := th.Registers() 251 if err != nil { 252 state.setErr(err) 253 return notes 254 } 255 256 for _, specialReg := range []uint64{regs.PC(), regs.SP(), regs.BP(), regs.TLS()} { 257 binary.Write(buf, binary.LittleEndian, specialReg) 258 } 259 260 gaddr, hasGaddr := regs.GAddr() 261 binary.Write(buf, binary.LittleEndian, hasGaddr) 262 binary.Write(buf, binary.LittleEndian, gaddr) 263 264 regsv, err := regs.Slice(true) 265 if err != nil { 266 state.setErr(err) 267 return notes 268 } 269 270 binary.Write(buf, binary.LittleEndian, uint32(len(regsv))) 271 272 for _, reg := range regsv { 273 binary.Write(buf, binary.LittleEndian, uint16(len(reg.Name))) 274 buf.Write([]byte(reg.Name)) 275 if reg.Reg.Bytes != nil { 276 binary.Write(buf, binary.LittleEndian, uint16(len(reg.Reg.Bytes))) 277 buf.Write(reg.Reg.Bytes) 278 } else { 279 binary.Write(buf, binary.LittleEndian, uint16(8)) 280 binary.Write(buf, binary.LittleEndian, reg.Reg.Uint64Val) 281 } 282 } 283 284 return append(notes, elfwriter.Note{ 285 Type: elfwriter.DelveThreadNodeType, 286 Name: "", 287 Data: buf.Bytes(), 288 }) 289 } 290 291 func (t *Target) dumpMemory(state *DumpState, w *elfwriter.Writer, mme *MemoryMapEntry) { 292 var flags elf.ProgFlag 293 if mme.Read { 294 flags |= elf.PF_R 295 } 296 if mme.Write { 297 flags |= elf.PF_W 298 } 299 if mme.Exec { 300 flags |= elf.PF_X 301 } 302 303 w.Progs = append(w.Progs, &elf.ProgHeader{ 304 Type: elf.PT_LOAD, 305 Flags: flags, 306 Off: uint64(w.Here()), 307 Vaddr: mme.Addr, 308 Paddr: 0, 309 Filesz: mme.Size, 310 Memsz: mme.Size, 311 Align: 0, 312 }) 313 314 buf := make([]byte, 1024*1024) 315 addr := mme.Addr 316 sz := mme.Size 317 mem := t.Memory() 318 319 for sz > 0 { 320 if w.Err != nil { 321 state.setErr(fmt.Errorf("error writing to output file: %v", w.Err)) 322 return 323 } 324 if state.isCanceled() { 325 return 326 } 327 chunk := buf 328 if uint64(len(chunk)) > sz { 329 chunk = chunk[:sz] 330 } 331 n, err := mem.ReadMemory(chunk, addr) 332 for i := n; i < len(chunk); i++ { 333 chunk[i] = 0 334 } 335 // Errors and short reads are ignored, the most likely reason is that 336 // (*ProcessInternal).MemoryMap gave us a bad mapping that can't be read 337 // and the behavior that's maximally useful to the user is to generate an 338 // incomplete dump. 339 w.Write(chunk) 340 addr += uint64(len(chunk)) 341 sz -= uint64(len(chunk)) 342 if err == nil { 343 state.memDone(uint64(len(chunk))) 344 } 345 } 346 } 347 348 func (t *Target) shouldDumpMemory(mme *MemoryMapEntry) bool { 349 if !mme.Read { 350 return false 351 } 352 exeimg := t.BinInfo().Images[0] 353 if mme.Write || mme.Filename == "" || mme.Filename != exeimg.Path { 354 return true 355 } 356 isgo := false 357 for _, cu := range exeimg.compileUnits { 358 if cu.isgo { 359 isgo = true 360 break 361 } 362 } 363 if !isgo { 364 return true 365 } 366 367 exe, err := elf.Open(exeimg.Path) 368 if err != nil { 369 return true 370 } 371 372 if exe.Type != elf.ET_EXEC { 373 return true 374 } 375 376 for _, prog := range exe.Progs { 377 if prog.Type == elf.PT_LOAD && (prog.Flags&elf.PF_W == 0) && (prog.Flags&elf.PF_R != 0) && (prog.Vaddr == mme.Addr) && (prog.Memsz == mme.Size) && (prog.Off == mme.Offset) { 378 return false 379 } 380 } 381 return true 382 } 383 384 type internalError struct { 385 Err interface{} 386 Stack []internalErrorFrame 387 } 388 389 type internalErrorFrame struct { 390 Pc uintptr 391 Func string 392 File string 393 Line int 394 } 395 396 func newInternalError(ierr interface{}, skip int) *internalError { 397 r := &internalError{ierr, nil} 398 for i := skip; ; i++ { 399 pc, file, line, ok := runtime.Caller(i) 400 if !ok { 401 break 402 } 403 fname := "<unknown>" 404 fn := runtime.FuncForPC(pc) 405 if fn != nil { 406 fname = fn.Name() 407 } 408 r.Stack = append(r.Stack, internalErrorFrame{pc, fname, file, line}) 409 } 410 return r 411 } 412 413 func (err *internalError) Error() string { 414 var out bytes.Buffer 415 fmt.Fprintf(&out, "Internal debugger error: %v\n", err.Err) 416 for _, frame := range err.Stack { 417 fmt.Fprintf(&out, "%s (%#x)\n\t%s:%d\n", frame.Func, frame.Pc, frame.File, frame.Line) 418 } 419 return out.String() 420 }