1 | // Licensed to the .NET Foundation under one or more agreements. |
2 | // The .NET Foundation licenses this file to you under the MIT license. |
3 | // See the LICENSE file in the project root for more information. |
4 | |
5 | #include "createdump.h" |
6 | |
7 | DumpWriter::DumpWriter(CrashInfo& crashInfo) : |
8 | m_ref(1), |
9 | m_fd(-1), |
10 | m_crashInfo(crashInfo) |
11 | { |
12 | m_crashInfo.AddRef(); |
13 | } |
14 | |
15 | DumpWriter::~DumpWriter() |
16 | { |
17 | if (m_fd != -1) |
18 | { |
19 | close(m_fd); |
20 | m_fd = -1; |
21 | } |
22 | m_crashInfo.Release(); |
23 | } |
24 | |
25 | STDMETHODIMP |
26 | DumpWriter::QueryInterface( |
27 | ___in REFIID InterfaceId, |
28 | ___out PVOID* Interface) |
29 | { |
30 | if (InterfaceId == IID_IUnknown) |
31 | { |
32 | *Interface = (IUnknown*)this; |
33 | AddRef(); |
34 | return S_OK; |
35 | } |
36 | else |
37 | { |
38 | *Interface = NULL; |
39 | return E_NOINTERFACE; |
40 | } |
41 | } |
42 | |
43 | STDMETHODIMP_(ULONG) |
44 | DumpWriter::AddRef() |
45 | { |
46 | LONG ref = InterlockedIncrement(&m_ref); |
47 | return ref; |
48 | } |
49 | |
50 | STDMETHODIMP_(ULONG) |
51 | DumpWriter::Release() |
52 | { |
53 | LONG ref = InterlockedDecrement(&m_ref); |
54 | if (ref == 0) |
55 | { |
56 | delete this; |
57 | } |
58 | return ref; |
59 | } |
60 | |
61 | bool |
62 | DumpWriter::OpenDump(const char* dumpFileName) |
63 | { |
64 | m_fd = open(dumpFileName, O_WRONLY|O_CREAT|O_TRUNC, 0664); |
65 | if (m_fd == -1) |
66 | { |
67 | fprintf(stderr, "Could not open output %s: %s\n" , dumpFileName, strerror(errno)); |
68 | return false; |
69 | } |
70 | return true; |
71 | } |
72 | |
73 | // Write the core dump file: |
74 | // ELF header |
75 | // Single section header (Shdr) for 64 bit program header count |
76 | // Phdr for the PT_NOTE |
77 | // PT_LOAD |
78 | // PT_NOTEs |
79 | // process info (prpsinfo_t) |
80 | // NT_FILE entries |
81 | // threads |
82 | // alignment |
83 | // memory blocks |
84 | bool |
85 | DumpWriter::WriteDump() |
86 | { |
87 | // Write the ELF header |
88 | Ehdr ehdr; |
89 | memset(&ehdr, 0, sizeof(Ehdr)); |
90 | ehdr.e_ident[0] = ELFMAG0; |
91 | ehdr.e_ident[1] = ELFMAG1; |
92 | ehdr.e_ident[2] = ELFMAG2; |
93 | ehdr.e_ident[3] = ELFMAG3; |
94 | ehdr.e_ident[EI_CLASS] = ELF_CLASS; |
95 | ehdr.e_ident[EI_DATA] = ELFDATA2LSB; |
96 | ehdr.e_ident[EI_VERSION] = EV_CURRENT; |
97 | ehdr.e_ident[EI_OSABI] = ELFOSABI_LINUX; |
98 | |
99 | ehdr.e_type = ET_CORE; |
100 | ehdr.e_machine = ELF_ARCH; |
101 | ehdr.e_version = EV_CURRENT; |
102 | ehdr.e_shoff = sizeof(Ehdr); |
103 | ehdr.e_phoff = sizeof(Ehdr) + sizeof(Shdr); |
104 | |
105 | ehdr.e_ehsize = sizeof(Ehdr); |
106 | ehdr.e_phentsize = sizeof(Phdr); |
107 | ehdr.e_shentsize = sizeof(Shdr); |
108 | |
109 | // The ELF header only allows UINT16 for the number of program |
110 | // headers. In a core dump this equates to PT_NODE and PT_LOAD. |
111 | // |
112 | // When more program headers than 65534 the first section entry |
113 | // is used to store the actual program header count. |
114 | |
115 | // PT_NOTE + number of memory regions |
116 | uint64_t phnum = 1 + m_crashInfo.MemoryRegions().size(); |
117 | |
118 | if (phnum < PH_HDR_CANARY) { |
119 | ehdr.e_phnum = phnum; |
120 | } |
121 | else { |
122 | ehdr.e_phnum = PH_HDR_CANARY; |
123 | } |
124 | |
125 | if (!WriteData(&ehdr, sizeof(Ehdr))) { |
126 | return false; |
127 | } |
128 | |
129 | size_t offset = sizeof(Ehdr) + sizeof(Shdr) + (phnum * sizeof(Phdr)); |
130 | size_t filesz = GetProcessInfoSize() + GetAuxvInfoSize() + GetThreadInfoSize() + GetNTFileInfoSize(); |
131 | |
132 | // Add single section containing the actual count |
133 | // of the program headers to be written. |
134 | Shdr shdr; |
135 | memset(&shdr, 0, sizeof(shdr)); |
136 | shdr.sh_info = phnum; |
137 | // When section header offset is present but ehdr section num = 0 |
138 | // then is is expected that the sh_size indicates the size of the |
139 | // section array or 1 in our case. |
140 | shdr.sh_size = 1; |
141 | if (!WriteData(&shdr, sizeof(shdr))) { |
142 | return false; |
143 | } |
144 | |
145 | // PT_NOTE header |
146 | Phdr phdr; |
147 | memset(&phdr, 0, sizeof(Phdr)); |
148 | phdr.p_type = PT_NOTE; |
149 | phdr.p_offset = offset; |
150 | phdr.p_filesz = filesz; |
151 | |
152 | if (!WriteData(&phdr, sizeof(phdr))) { |
153 | return false; |
154 | } |
155 | |
156 | // PT_NOTE sections must end on 4 byte boundary |
157 | // We output the NT_FILE, AUX and Thread entries |
158 | // AUX is aligned, NT_FILE is aligned and then we |
159 | // check to pad end of the thread list |
160 | phdr.p_type = PT_LOAD; |
161 | phdr.p_align = 4096; |
162 | |
163 | size_t finalNoteAlignment = phdr.p_align - ((offset + filesz) % phdr.p_align); |
164 | if (finalNoteAlignment == phdr.p_align) { |
165 | finalNoteAlignment = 0; |
166 | } |
167 | offset += finalNoteAlignment; |
168 | |
169 | TRACE("Writing memory region headers to core file\n" ); |
170 | |
171 | // Write memory region note headers |
172 | for (const MemoryRegion& memoryRegion : m_crashInfo.MemoryRegions()) |
173 | { |
174 | phdr.p_flags = memoryRegion.Permissions(); |
175 | phdr.p_vaddr = memoryRegion.StartAddress(); |
176 | phdr.p_memsz = memoryRegion.Size(); |
177 | |
178 | if (memoryRegion.IsBackedByMemory()) |
179 | { |
180 | offset += filesz; |
181 | phdr.p_filesz = filesz = memoryRegion.Size(); |
182 | phdr.p_offset = offset; |
183 | } |
184 | else |
185 | { |
186 | phdr.p_filesz = 0; |
187 | phdr.p_offset = 0; |
188 | } |
189 | |
190 | if (!WriteData(&phdr, sizeof(phdr))) { |
191 | return false; |
192 | } |
193 | } |
194 | |
195 | // Write process info data to core file |
196 | if (!WriteProcessInfo()) { |
197 | return false; |
198 | } |
199 | |
200 | // Write auxv data to core file |
201 | if (!WriteAuxv()) { |
202 | return false; |
203 | } |
204 | |
205 | // Write NT_FILE entries to the core file |
206 | if (!WriteNTFileInfo()) { |
207 | return false; |
208 | } |
209 | |
210 | TRACE("Writing %zd thread entries to core file\n" , m_crashInfo.Threads().size()); |
211 | |
212 | // Write all the thread's state and registers |
213 | for (const ThreadInfo* thread : m_crashInfo.Threads()) |
214 | { |
215 | if (!WriteThread(*thread, SIGABRT)) { |
216 | return false; |
217 | } |
218 | } |
219 | |
220 | // Zero out the end of the PT_NOTE section to the boundary |
221 | // and then laydown the memory blocks |
222 | if (finalNoteAlignment > 0) { |
223 | assert(finalNoteAlignment < sizeof(m_tempBuffer)); |
224 | memset(m_tempBuffer, 0, finalNoteAlignment); |
225 | if (!WriteData(m_tempBuffer, finalNoteAlignment)) { |
226 | return false; |
227 | } |
228 | } |
229 | |
230 | TRACE("Writing %zd memory regions to core file\n" , m_crashInfo.MemoryRegions().size()); |
231 | |
232 | // Read from target process and write memory regions to core |
233 | uint64_t total = 0; |
234 | for (const MemoryRegion& memoryRegion : m_crashInfo.MemoryRegions()) |
235 | { |
236 | // Only write the regions that are backed by memory |
237 | if (memoryRegion.IsBackedByMemory()) |
238 | { |
239 | uint32_t size = memoryRegion.Size(); |
240 | uint64_t address = memoryRegion.StartAddress(); |
241 | total += size; |
242 | |
243 | while (size > 0) |
244 | { |
245 | uint32_t bytesToRead = std::min(size, (uint32_t)sizeof(m_tempBuffer)); |
246 | uint32_t read = 0; |
247 | |
248 | if (FAILED(m_crashInfo.DataTarget()->ReadVirtual(address, m_tempBuffer, bytesToRead, &read))) { |
249 | fprintf(stderr, "ReadVirtual(%" PRIA PRIx64 ", %08x) FAILED\n" , address, bytesToRead); |
250 | return false; |
251 | } |
252 | |
253 | if (!WriteData(m_tempBuffer, read)) { |
254 | return false; |
255 | } |
256 | |
257 | address += read; |
258 | size -= read; |
259 | } |
260 | } |
261 | } |
262 | |
263 | printf("Written %" PRId64 " bytes (%" PRId64 " pages) to core file\n" , total, total / PAGE_SIZE); |
264 | |
265 | return true; |
266 | } |
267 | |
268 | bool |
269 | DumpWriter::WriteProcessInfo() |
270 | { |
271 | prpsinfo_t processInfo; |
272 | memset(&processInfo, 0, sizeof(processInfo)); |
273 | processInfo.pr_sname = 'R'; |
274 | processInfo.pr_pid = m_crashInfo.Pid(); |
275 | processInfo.pr_ppid = m_crashInfo.Ppid(); |
276 | processInfo.pr_pgrp = m_crashInfo.Tgid(); |
277 | strcpy_s(processInfo.pr_fname, sizeof(processInfo.pr_fname), m_crashInfo.Name()); |
278 | |
279 | Nhdr nhdr; |
280 | memset(&nhdr, 0, sizeof(nhdr)); |
281 | nhdr.n_namesz = 5; |
282 | nhdr.n_descsz = sizeof(prpsinfo_t); |
283 | nhdr.n_type = NT_PRPSINFO; |
284 | |
285 | TRACE("Writing process information to core file\n" ); |
286 | |
287 | // Write process info data to core file |
288 | if (!WriteData(&nhdr, sizeof(nhdr)) || |
289 | !WriteData("CORE\0PRP" , 8) || |
290 | !WriteData(&processInfo, sizeof(prpsinfo_t))) { |
291 | return false; |
292 | } |
293 | return true; |
294 | } |
295 | |
296 | bool |
297 | DumpWriter::WriteAuxv() |
298 | { |
299 | Nhdr nhdr; |
300 | memset(&nhdr, 0, sizeof(nhdr)); |
301 | nhdr.n_namesz = 5; |
302 | nhdr.n_descsz = m_crashInfo.GetAuxvSize(); |
303 | nhdr.n_type = NT_AUXV; |
304 | |
305 | TRACE("Writing %zd auxv entries to core file\n" , m_crashInfo.AuxvEntries().size()); |
306 | |
307 | if (!WriteData(&nhdr, sizeof(nhdr)) || |
308 | !WriteData("CORE\0AUX" , 8)) { |
309 | return false; |
310 | } |
311 | for (const auto& auxvEntry : m_crashInfo.AuxvEntries()) |
312 | { |
313 | if (!WriteData(&auxvEntry, sizeof(auxvEntry))) { |
314 | return false; |
315 | } |
316 | } |
317 | return true; |
318 | } |
319 | |
320 | struct NTFileEntry |
321 | { |
322 | unsigned long StartAddress; |
323 | unsigned long EndAddress; |
324 | unsigned long Offset; |
325 | }; |
326 | |
327 | // Calculate the NT_FILE entries total size |
328 | size_t |
329 | DumpWriter::GetNTFileInfoSize(size_t* alignmentBytes) |
330 | { |
331 | size_t count = m_crashInfo.ModuleMappings().size(); |
332 | size_t size = 0; |
333 | |
334 | // Header, CORE, entry count, page size |
335 | size = sizeof(Nhdr) + 8 + sizeof(count) + sizeof(size); |
336 | |
337 | // start_address, end_address, offset |
338 | size += count * sizeof(NTFileEntry); |
339 | |
340 | // \0 terminator for each filename |
341 | size += count; |
342 | |
343 | // File name storage needed |
344 | for (const MemoryRegion& image : m_crashInfo.ModuleMappings()) { |
345 | size += strlen(image.FileName()); |
346 | } |
347 | // Notes must end on 4 byte alignment |
348 | size_t alignmentBytesNeeded = 4 - (size % 4); |
349 | size += alignmentBytesNeeded; |
350 | |
351 | if (alignmentBytes != nullptr) { |
352 | *alignmentBytes = alignmentBytesNeeded; |
353 | } |
354 | return size; |
355 | } |
356 | |
357 | // Write NT_FILE entries to the PT_NODE section |
358 | // |
359 | // Nhdr (NT_FILE) |
360 | // Total entries |
361 | // Page size |
362 | // [0] start_address end_address offset |
363 | // [1] start_address end_address offset |
364 | // [file name]\0[file name]\0... |
365 | bool |
366 | DumpWriter::WriteNTFileInfo() |
367 | { |
368 | Nhdr nhdr; |
369 | memset(&nhdr, 0, sizeof(nhdr)); |
370 | |
371 | // CORE + \0 and we align on 4 byte boundary |
372 | // so we can use CORE\0FIL for easier hex debugging |
373 | nhdr.n_namesz = 5; |
374 | nhdr.n_type = NT_FILE; // "FILE" |
375 | |
376 | // Size of payload for NT_FILE after CORE tag written |
377 | size_t alignmentBytesNeeded = 0; |
378 | nhdr.n_descsz = GetNTFileInfoSize(&alignmentBytesNeeded) - sizeof(nhdr) - 8; |
379 | |
380 | size_t count = m_crashInfo.ModuleMappings().size(); |
381 | size_t pageSize = PAGE_SIZE; |
382 | |
383 | TRACE("Writing %zd NT_FILE entries to core file\n" , m_crashInfo.ModuleMappings().size()); |
384 | |
385 | if (!WriteData(&nhdr, sizeof(nhdr)) || |
386 | !WriteData("CORE\0FIL" , 8) || |
387 | !WriteData(&count, sizeof(count)) || |
388 | !WriteData(&pageSize, sizeof(pageSize))) { |
389 | return false; |
390 | } |
391 | |
392 | for (const MemoryRegion& image : m_crashInfo.ModuleMappings()) |
393 | { |
394 | struct NTFileEntry entry { image.StartAddress(), image.EndAddress(), image.Offset() }; |
395 | if (!WriteData(&entry, sizeof(entry))) { |
396 | return false; |
397 | } |
398 | } |
399 | |
400 | for (const MemoryRegion& image : m_crashInfo.ModuleMappings()) |
401 | { |
402 | if (!WriteData(image.FileName(), strlen(image.FileName())) || |
403 | !WriteData("\0" , 1)) { |
404 | return false; |
405 | } |
406 | } |
407 | |
408 | // Has to end on a 4 byte boundary. Debugger, readelf and such |
409 | // will automatically align on next 4 bytes and look for a PT_NOTE |
410 | // header. |
411 | if (alignmentBytesNeeded) { |
412 | if (!WriteData("\0\0\0\0" , alignmentBytesNeeded)) { |
413 | return false; |
414 | } |
415 | } |
416 | |
417 | return true; |
418 | } |
419 | |
420 | bool |
421 | DumpWriter::WriteThread(const ThreadInfo& thread, int fatal_signal) |
422 | { |
423 | prstatus_t pr; |
424 | memset(&pr, 0, sizeof(pr)); |
425 | |
426 | pr.pr_info.si_signo = fatal_signal; |
427 | pr.pr_cursig = fatal_signal; |
428 | pr.pr_pid = thread.Tid(); |
429 | pr.pr_ppid = thread.Ppid(); |
430 | pr.pr_pgrp = thread.Tgid(); |
431 | memcpy(&pr.pr_reg, thread.GPRegisters(), sizeof(user_regs_struct)); |
432 | |
433 | Nhdr nhdr; |
434 | memset(&nhdr, 0, sizeof(nhdr)); |
435 | |
436 | // Name size is CORE plus the NULL terminator |
437 | // The format requires 4 byte alignment so the |
438 | // value written in 8 bytes. Stuff the last 3 |
439 | // bytes with the type of NT_PRSTATUS so it is |
440 | // easier to debug in a hex editor. |
441 | nhdr.n_namesz = 5; |
442 | nhdr.n_descsz = sizeof(prstatus_t); |
443 | nhdr.n_type = NT_PRSTATUS; |
444 | if (!WriteData(&nhdr, sizeof(nhdr)) || |
445 | !WriteData("CORE\0THR" , 8) || |
446 | !WriteData(&pr, sizeof(prstatus_t))) { |
447 | return false; |
448 | } |
449 | |
450 | #if defined(__i386__) || defined(__x86_64__) || defined(__arm__) |
451 | nhdr.n_descsz = sizeof(user_fpregs_struct); |
452 | nhdr.n_type = NT_FPREGSET; |
453 | if (!WriteData(&nhdr, sizeof(nhdr)) || |
454 | !WriteData("CORE\0FLT" , 8) || |
455 | !WriteData(thread.FPRegisters(), sizeof(user_fpregs_struct))) { |
456 | return false; |
457 | } |
458 | #endif |
459 | |
460 | nhdr.n_namesz = 6; |
461 | |
462 | #if defined(__i386__) |
463 | nhdr.n_descsz = sizeof(user_fpxregs_struct); |
464 | nhdr.n_type = NT_PRXFPREG; |
465 | if (!WriteData(&nhdr, sizeof(nhdr)) || |
466 | !WriteData("LINUX\0\0\0" , 8) || |
467 | !WriteData(thread.FPXRegisters(), sizeof(user_fpxregs_struct))) { |
468 | return false; |
469 | } |
470 | #endif |
471 | |
472 | #if defined(__arm__) && defined(__VFP_FP__) && !defined(__SOFTFP__) |
473 | nhdr.n_descsz = sizeof(user_vfpregs_struct); |
474 | nhdr.n_type = NT_ARM_VFP; |
475 | if (!WriteData(&nhdr, sizeof(nhdr)) || |
476 | !WriteData("LINUX\0\0\0" , 8) || |
477 | !WriteData(thread.VFPRegisters(), sizeof(user_vfpregs_struct))) { |
478 | return false; |
479 | } |
480 | #endif |
481 | |
482 | return true; |
483 | } |
484 | |
485 | // Write all of the given buffer, handling short writes and EINTR. Return true iff successful. |
486 | bool |
487 | DumpWriter::WriteData(const void* buffer, size_t length) |
488 | { |
489 | const uint8_t* data = (const uint8_t*)buffer; |
490 | |
491 | size_t done = 0; |
492 | while (done < length) { |
493 | ssize_t written; |
494 | do { |
495 | written = write(m_fd, data + done, length - done); |
496 | } while (written == -1 && errno == EINTR); |
497 | |
498 | if (written < 1) { |
499 | fprintf(stderr, "WriteData FAILED %s\n" , strerror(errno)); |
500 | return false; |
501 | } |
502 | done += written; |
503 | } |
504 | return true; |
505 | } |
506 | |