VirtualBox

source: vbox/trunk/src/VBox/Debugger/DBGPlugInDarwin.cpp@ 83083

Last change on this file since 83083 was 83083, checked in by vboxsync, 5 years ago

DBGPlugInDarwin.cpp: Hacking.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 39.3 KB
Line 
1/* $Id: DBGPlugInDarwin.cpp 83083 2020-02-15 02:23:57Z vboxsync $ */
2/** @file
3 * DBGPlugInDarwin - Debugger and Guest OS Digger Plugin For Darwin / OS X.
4 */
5
6/*
7 * Copyright (C) 2008-2020 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18
19/*********************************************************************************************************************************
20* Header Files *
21*********************************************************************************************************************************/
22#define LOG_GROUP LOG_GROUP_DBGF /// @todo add new log group.
23#include "DBGPlugIns.h"
24#include <VBox/vmm/dbgf.h>
25#include <iprt/err.h>
26#include <iprt/mem.h>
27#include <iprt/stream.h>
28#include <iprt/string.h>
29#include <iprt/uuid.h>
30#include <iprt/ctype.h>
31#include <iprt/formats/mach-o.h>
32
33#undef LogRel2
34#define LogRel2 LogRel
35
36
37/*********************************************************************************************************************************
38* Structures and Typedefs *
39*********************************************************************************************************************************/
40
41/** @name Internal Darwin structures
42 * @{ */
43
44/**
45 * 32-bit darwin kernel module info structure (kmod_info_t).
46 */
47typedef struct OSX32_kmod_info
48{
49 uint32_t next;
50 int32_t info_version;
51 uint32_t id;
52 char name[64];
53 char version[64];
54 int32_t reference_count;
55 uint32_t reference_list; /**< Points to kmod_reference_t. */
56 uint32_t address; /**< Where in memory the kext is loaded. */
57 uint32_t size;
58 uint32_t hdr_size;
59 uint32_t start; /**< Address of kmod_start_func_t. */
60 uint32_t stop; /**< Address of kmod_stop_func_t. */
61} OSX32_kmod_info_t;
62
63/**
64 * 32-bit darwin kernel module info structure (kmod_info_t).
65 */
66#pragma pack(1)
67typedef struct OSX64_kmod_info
68{
69 uint64_t next;
70 int32_t info_version;
71 uint32_t id;
72 char name[64];
73 char version[64];
74 int32_t reference_count;
75 uint64_t reference_list; /**< Points to kmod_reference_t. Misaligned, duh. */
76 uint64_t address; /**< Where in memory the kext is loaded. */
77 uint64_t size;
78 uint64_t hdr_size;
79 uint64_t start; /**< Address of kmod_start_func_t. */
80 uint64_t stop; /**< Address of kmod_stop_func_t. */
81} OSX64_kmod_info_t;
82#pragma pack()
83
84/** The value of the info_version field. */
85#define OSX_KMOD_INFO_VERSION INT32_C(1)
86
87/** @} */
88
89
90/**
91 * Linux guest OS digger instance data.
92 */
93typedef struct DBGDIGGERDARWIN
94{
95 /** Whether the information is valid or not.
96 * (For fending off illegal interface method calls.) */
97 bool fValid;
98
99 /** Set if 64-bit kernel, clear if 32-bit.
100 * Set during probing. */
101 bool f64Bit;
102 /** The address of an kernel version string (there are several).
103 * This is set during probing. */
104 DBGFADDRESS AddrKernelVersion;
105 /** Kernel base address.
106 * This is set during probing. */
107 DBGFADDRESS AddrKernel;
108
109 /** The kernel message log interface. */
110 DBGFOSIDMESG IDmesg;
111} DBGDIGGERDARWIN;
112/** Pointer to the linux guest OS digger instance data. */
113typedef DBGDIGGERDARWIN *PDBGDIGGERDARWIN;
114
115
116/*********************************************************************************************************************************
117* Defined Constants And Macros *
118*********************************************************************************************************************************/
119/** Validates a 32-bit darwin kernel address */
120#define OSX32_VALID_ADDRESS(Addr) ((Addr) > UINT32_C(0x00001000) && (Addr) < UINT32_C(0xfffff000))
121/** Validates a 64-bit darwin kernel address */
122#define OSX64_VALID_ADDRESS(Addr) ((Addr) > UINT64_C(0xffff800000000000) && (Addr) < UINT64_C(0xfffffffffffff000))
123/** Validates a 32-bit or 64-bit darwin kernel address. */
124#define OSX_VALID_ADDRESS(a_f64Bits, a_Addr) \
125 ((a_f64Bits) ? OSX64_VALID_ADDRESS(a_Addr) : OSX32_VALID_ADDRESS(a_Addr))
126
127/** AppleOsX on little endian ASCII systems. */
128#define DIG_DARWIN_MOD_TAG UINT64_C(0x58734f656c707041)
129
130
131/*********************************************************************************************************************************
132* Internal Functions *
133*********************************************************************************************************************************/
134static DECLCALLBACK(int) dbgDiggerDarwinInit(PUVM pUVM, void *pvData);
135
136
137
138/**
139 * @interface_method_impl{DBGFOSIDMESG,pfnQueryKernelLog}
140 */
141static DECLCALLBACK(int) dbgDiggerDarwinIDmsg_QueryKernelLog(PDBGFOSIDMESG pThis, PUVM pUVM, uint32_t fFlags, uint32_t cMessages,
142 char *pszBuf, size_t cbBuf, size_t *pcbActual)
143{
144 RT_NOREF1(fFlags);
145 PDBGDIGGERDARWIN pData = RT_FROM_MEMBER(pThis, DBGDIGGERDARWIN, IDmesg);
146
147 if (cMessages < 1)
148 return VERR_INVALID_PARAMETER;
149
150 /*
151 * The 'msgbufp' variable points to a struct msgbuf (bsd/kern/subr_log.c).
152 */
153 RTDBGAS hAs = DBGFR3AsResolveAndRetain(pUVM, DBGF_AS_KERNEL);
154 RTDBGMOD hMod;
155 int rc = RTDbgAsModuleByName(hAs, "mach_kernel", 0, &hMod);
156 if (RT_FAILURE(rc))
157 return VERR_NOT_FOUND;
158 RTDbgAsRelease(hAs);
159
160 DBGFADDRESS Addr;
161 RTGCPTR GCPtrMsgBufP = 0;
162 RTDBGSYMBOL SymInfo;
163 rc = RTDbgModSymbolByName(hMod, "_msgbufp", &SymInfo);
164 if (RT_SUCCESS(rc))
165 {
166 rc = DBGFR3MemRead(pUVM, 0 /*idCpu*/, DBGFR3AddrFromFlat(pUVM, &Addr, SymInfo.Value + pData->AddrKernel.FlatPtr),
167 &GCPtrMsgBufP, pData->f64Bit ? sizeof(uint64_t) : sizeof(uint32_t));
168 if (RT_FAILURE(rc))
169 {
170 LogRel(("dbgDiggerDarwinIDmsg_QueryKernelLog: failed to read _msgbufp at %RGv: %Rrc\n", Addr.FlatPtr, rc));
171 return VERR_NOT_FOUND;
172 }
173 if (!OSX_VALID_ADDRESS(pData->f64Bit, GCPtrMsgBufP))
174 {
175 LogRel(("dbgDiggerDarwinIDmsg_QueryKernelLog: Invalid address for _msgbufp: %RGv\n", GCPtrMsgBufP));
176 return VERR_NOT_FOUND;
177 }
178 }
179 else
180 {
181 rc = RTDbgModSymbolByName(hMod, "_msgbuf", &SymInfo);
182 if (RT_FAILURE(rc))
183 {
184 LogRel(("dbgDiggerDarwinIDmsg_QueryKernelLog: failed to find _msgbufp and _msgbuf: %Rrc\n", rc));
185 return VERR_NOT_FOUND;
186 }
187 GCPtrMsgBufP = SymInfo.Value + pData->AddrKernel.FlatPtr;
188 if (!OSX_VALID_ADDRESS(pData->f64Bit, GCPtrMsgBufP))
189 {
190 LogRel(("dbgDiggerDarwinIDmsg_QueryKernelLog: Invalid address for _msgbuf: %RGv\n", GCPtrMsgBufP));
191 return VERR_NOT_FOUND;
192 }
193 }
194
195 /*
196 * Read the msgbuf structure.
197 */
198 struct
199 {
200 uint32_t msg_magic;
201 uint32_t msg_size;
202 uint32_t msg_bufx;
203 uint32_t msg_bufr;
204 uint64_t msg_bufc; /**< Size depends on windows size. */
205 } MsgBuf;
206 rc = DBGFR3MemRead(pUVM, 0 /*idCpu*/, DBGFR3AddrFromFlat(pUVM, &Addr, GCPtrMsgBufP),
207 &MsgBuf, sizeof(MsgBuf) - (pData->f64Bit ? 0 : sizeof(uint32_t)) );
208 if (RT_FAILURE(rc))
209 {
210 LogRel(("dbgDiggerDarwinIDmsg_QueryKernelLog: failed to read msgbuf struct at %RGv: %Rrc\n", Addr.FlatPtr, rc));
211 return VERR_NOT_FOUND;
212 }
213 if (!pData->f64Bit)
214 MsgBuf.msg_bufc &= UINT32_MAX;
215
216 /*
217 * Validate the structure.
218 */
219 if ( MsgBuf.msg_magic != UINT32_C(0x63061)
220 || MsgBuf.msg_size < UINT32_C(4096)
221 || MsgBuf.msg_size > 16*_1M
222 || MsgBuf.msg_bufx > MsgBuf.msg_size
223 || MsgBuf.msg_bufr > MsgBuf.msg_size
224 || !OSX_VALID_ADDRESS(pData->f64Bit, MsgBuf.msg_bufc) )
225 {
226 LogRel(("dbgDiggerDarwinIDmsg_QueryKernelLog: Invalid MsgBuf data: magic=%#x size=%#x bufx=%#x bufr=%#x bufc=%RGv\n",
227 MsgBuf.msg_magic, MsgBuf.msg_size, MsgBuf.msg_bufx, MsgBuf.msg_bufr, MsgBuf.msg_bufc));
228 return VERR_INVALID_STATE;
229 }
230
231 /*
232 * Read the buffer.
233 */
234 char *pchMsgBuf = (char *)RTMemAlloc(MsgBuf.msg_size);
235 if (!pchMsgBuf)
236 {
237 LogRel(("dbgDiggerDarwinIDmsg_QueryKernelLog: Failed to allocate %#x bytes of memory for the log buffer\n",
238 MsgBuf.msg_size));
239 return VERR_INVALID_STATE;
240 }
241 rc = DBGFR3MemRead(pUVM, 0 /*idCpu*/, DBGFR3AddrFromFlat(pUVM, &Addr, MsgBuf.msg_bufc), pchMsgBuf, MsgBuf.msg_size);
242 if (RT_SUCCESS(rc))
243 {
244 /*
245 * Copy it out raw.
246 */
247 uint32_t offDst = 0;
248 if (MsgBuf.msg_bufr < MsgBuf.msg_bufx)
249 {
250 /* Single chunk between the read and write offsets. */
251 uint32_t cbToCopy = MsgBuf.msg_bufx - MsgBuf.msg_bufr;
252 if (cbToCopy < cbBuf)
253 {
254 memcpy(pszBuf, &pchMsgBuf[MsgBuf.msg_bufr], cbToCopy);
255 pszBuf[cbToCopy] = '\0';
256 rc = VINF_SUCCESS;
257 }
258 else
259 {
260 if (cbBuf)
261 {
262 memcpy(pszBuf, &pchMsgBuf[MsgBuf.msg_bufr], cbBuf - 1);
263 pszBuf[cbBuf - 1] = '\0';
264 }
265 rc = VERR_BUFFER_OVERFLOW;
266 }
267 offDst = cbToCopy + 1;
268 }
269 else
270 {
271 /* Two chunks, read offset to end, start to write offset. */
272 uint32_t cbFirst = MsgBuf.msg_size - MsgBuf.msg_bufr;
273 uint32_t cbSecond = MsgBuf.msg_bufx;
274 if (cbFirst + cbSecond < cbBuf)
275 {
276 memcpy(pszBuf, &pchMsgBuf[MsgBuf.msg_bufr], cbFirst);
277 memcpy(&pszBuf[cbFirst], pchMsgBuf, cbSecond);
278 offDst = cbFirst + cbSecond;
279 pszBuf[offDst++] = '\0';
280 rc = VINF_SUCCESS;
281 }
282 else
283 {
284 offDst = cbFirst + cbSecond + 1;
285 if (cbFirst < cbBuf)
286 {
287 memcpy(pszBuf, &pchMsgBuf[MsgBuf.msg_bufr], cbFirst);
288 memcpy(&pszBuf[cbFirst], pchMsgBuf, cbBuf - cbFirst);
289 pszBuf[cbBuf - 1] = '\0';
290 }
291 else if (cbBuf)
292 {
293 memcpy(pszBuf, &pchMsgBuf[MsgBuf.msg_bufr], cbBuf - 1);
294 pszBuf[cbBuf - 1] = '\0';
295 }
296 rc = VERR_BUFFER_OVERFLOW;
297 }
298 }
299
300 if (pcbActual)
301 *pcbActual = offDst;
302 }
303 else
304 LogRel(("dbgDiggerDarwinIDmsg_QueryKernelLog: Error reading %#x bytes at %RGv: %Rrc\n",
305 MsgBuf.msg_size, MsgBuf.msg_bufc, rc));
306 RTMemFree(pchMsgBuf);
307 return rc;
308}
309
310
311/**
312 * @copydoc DBGFOSREG::pfnStackUnwindAssist
313 */
314static DECLCALLBACK(int) dbgDiggerDarwinStackUnwindAssist(PUVM pUVM, void *pvData, VMCPUID idCpu, PDBGFSTACKFRAME pFrame,
315 PRTDBGUNWINDSTATE pState, PCCPUMCTX pInitialCtx, RTDBGAS hAs,
316 uint64_t *puScratch)
317{
318 RT_NOREF(pUVM, pvData, idCpu, pFrame, pState, pInitialCtx, hAs, puScratch);
319 return VINF_SUCCESS;
320}
321
322
323/**
324 * @copydoc DBGFOSREG::pfnQueryInterface
325 */
326static DECLCALLBACK(void *) dbgDiggerDarwinQueryInterface(PUVM pUVM, void *pvData, DBGFOSINTERFACE enmIf)
327{
328 RT_NOREF1(pUVM);
329 PDBGDIGGERDARWIN pThis = (PDBGDIGGERDARWIN)pvData;
330 switch (enmIf)
331 {
332 case DBGFOSINTERFACE_DMESG:
333 return &pThis->IDmesg;
334
335 default:
336 return NULL;
337 }
338}
339
340
341/**
342 * @copydoc DBGFOSREG::pfnQueryVersion
343 */
344static DECLCALLBACK(int) dbgDiggerDarwinQueryVersion(PUVM pUVM, void *pvData, char *pszVersion, size_t cchVersion)
345{
346 PDBGDIGGERDARWIN pThis = (PDBGDIGGERDARWIN)pvData;
347 Assert(pThis->fValid);
348
349 /*
350 * It's all in the linux banner.
351 */
352 int rc = DBGFR3MemReadString(pUVM, 0, &pThis->AddrKernelVersion, pszVersion, cchVersion);
353 if (RT_SUCCESS(rc))
354 {
355 char *pszEnd = RTStrEnd(pszVersion, cchVersion);
356 AssertReturn(pszEnd, VERR_BUFFER_OVERFLOW);
357 while ( pszEnd > pszVersion
358 && RT_C_IS_SPACE(pszEnd[-1]))
359 pszEnd--;
360 *pszEnd = '\0';
361 }
362 else
363 RTStrPrintf(pszVersion, cchVersion, "DBGFR3MemRead -> %Rrc", rc);
364
365 return rc;
366}
367
368
369/**
370 * @copydoc DBGFOSREG::pfnTerm
371 */
372static DECLCALLBACK(void) dbgDiggerDarwinTerm(PUVM pUVM, void *pvData)
373{
374 RT_NOREF1(pUVM);
375 PDBGDIGGERDARWIN pThis = (PDBGDIGGERDARWIN)pvData;
376
377 pThis->fValid = false;
378}
379
380
381/**
382 * @copydoc DBGFOSREG::pfnRefresh
383 */
384static DECLCALLBACK(int) dbgDiggerDarwinRefresh(PUVM pUVM, void *pvData)
385{
386 PDBGDIGGERDARWIN pThis = (PDBGDIGGERDARWIN)pvData;
387 NOREF(pThis);
388 Assert(pThis->fValid);
389
390 /*
391 * For now we'll flush and reload everything.
392 */
393 dbgDiggerDarwinTerm(pUVM, pvData);
394 return dbgDiggerDarwinInit(pUVM, pvData);
395}
396
397
398/**
399 * Helper function that tries to accertain whether a segment (__LINKEDIT) is
400 * present or not.
401 *
402 * @returns true if present, false if not.
403 * @param pUVM The user mode VM structure.
404 * @param uSegAddr The segment addresss.
405 * @param cbSeg The segment size.
406 * @param uMinAddr Lowest allowed address.
407 * @param uMaxAddr Highest allowed address.
408 */
409static int dbgDiggerDarwinIsSegmentPresent(PUVM pUVM, uint64_t uSegAddr, uint64_t cbSeg, uint64_t uMinAddr, uint64_t uMaxAddr)
410{
411 /*
412 * Validate the size and address.
413 */
414 if (cbSeg < 32)
415 {
416 LogRel(("OSXDig: __LINKEDIT too small %#RX64\n", cbSeg));
417 return false;
418 }
419 if (cbSeg > uMaxAddr - uMinAddr)
420 {
421 LogRel(("OSXDig: __LINKEDIT too big %#RX64, max %#RX64\n", cbSeg, uMaxAddr - uMinAddr));
422 return false;
423 }
424
425 if (uSegAddr < uMinAddr)
426 {
427 LogRel(("OSXDig: __LINKEDIT too low %#RX64, min %#RX64\n", uSegAddr, uMinAddr));
428 return false;
429 }
430 if (uSegAddr > uMaxAddr)
431 {
432 LogRel(("OSXDig: __LINKEDIT too high %#RX64, max %#RX64\n", uSegAddr, uMaxAddr));
433 return false;
434 }
435 if (uSegAddr + cbSeg > uMaxAddr)
436 {
437 LogRel(("OSXDig: __LINKEDIT ends too high %#RX64 (%#RX64+%#RX64), max %#RX64\n",
438 uSegAddr + cbSeg, uSegAddr, cbSeg, uMaxAddr));
439 return false;
440 }
441
442 /*
443 * Check that all the pages are present.
444 */
445 cbSeg += uSegAddr & X86_PAGE_OFFSET_MASK;
446 uSegAddr &= ~(uint64_t)X86_PAGE_OFFSET_MASK;
447 for (;;)
448 {
449 uint8_t abBuf[8];
450 DBGFADDRESS Addr;
451 int rc = DBGFR3MemRead(pUVM, 0 /*idCpu*/, DBGFR3AddrFromFlat(pUVM, &Addr, uSegAddr), abBuf, sizeof(abBuf));
452 if (RT_FAILURE(rc))
453 {
454 LogRel(("OSXDig: __LINKEDIT read error at %#RX64: %Rrc\n", uSegAddr, rc));
455 return false;
456 }
457
458 /* Advance */
459 if (cbSeg <= X86_PAGE_SIZE)
460 return true;
461 cbSeg -= X86_PAGE_SIZE;
462 uSegAddr += X86_PAGE_SIZE;
463 }
464}
465
466
467/**
468 * Helper function that validates a segment (or section) name.
469 *
470 * @returns true if valid, false if not.
471 * @param pszName The name string.
472 * @param cbName The size of the string, including terminator.
473 */
474static bool dbgDiggerDarwinIsValidSegOrSectName(const char *pszName, size_t cbName)
475{
476 /* ascii chars */
477 char ch;
478 size_t off = 0;
479 while (off < cbName && (ch = pszName[off]))
480 {
481 if (RT_C_IS_CNTRL(ch) || ch >= 127)
482 return false;
483 off++;
484 }
485
486 /* Not empty nor 100% full. */
487 if (off == 0 || off == cbName)
488 return false;
489
490 /* remainder should be zeros. */
491 while (off < cbName)
492 {
493 if (pszName[off])
494 return false;
495 off++;
496 }
497
498 return true;
499}
500
501
502static int dbgDiggerDarwinAddModule(PDBGDIGGERDARWIN pThis, PUVM pUVM, uint64_t uModAddr, const char *pszName, bool *pf64Bit)
503{
504 RT_NOREF1(pThis);
505 union
506 {
507 uint8_t ab[2 * X86_PAGE_4K_SIZE];
508 mach_header_64_t Hdr64;
509 mach_header_32_t Hdr32;
510 } uBuf;
511
512 /* Read the first page of the image. */
513 DBGFADDRESS ModAddr;
514 int rc = DBGFR3MemRead(pUVM, 0 /*idCpu*/, DBGFR3AddrFromFlat(pUVM, &ModAddr, uModAddr), uBuf.ab, X86_PAGE_4K_SIZE);
515 if (RT_FAILURE(rc))
516 return rc;
517
518 /* Validate the header. */
519 AssertCompileMembersSameSizeAndOffset(mach_header_64_t, magic, mach_header_32_t, magic);
520 if ( uBuf.Hdr64.magic != IMAGE_MACHO64_SIGNATURE
521 && uBuf.Hdr32.magic != IMAGE_MACHO32_SIGNATURE)
522 return VERR_INVALID_EXE_SIGNATURE;
523 AssertCompileMembersSameSizeAndOffset(mach_header_64_t, cputype, mach_header_32_t, cputype);
524 bool f64Bit = uBuf.Hdr64.magic == IMAGE_MACHO64_SIGNATURE;
525 if (uBuf.Hdr32.cputype != (f64Bit ? CPU_TYPE_X86_64 : CPU_TYPE_I386))
526 return VERR_LDR_ARCH_MISMATCH;
527 AssertCompileMembersSameSizeAndOffset(mach_header_64_t, filetype, mach_header_32_t, filetype);
528 if ( uBuf.Hdr32.filetype != MH_EXECUTE
529 && uBuf.Hdr32.filetype != (f64Bit ? MH_KEXT_BUNDLE : MH_OBJECT))
530 return VERR_BAD_EXE_FORMAT;
531 AssertCompileMembersSameSizeAndOffset(mach_header_64_t, ncmds, mach_header_32_t, ncmds);
532 if (uBuf.Hdr32.ncmds > 256)
533 return VERR_BAD_EXE_FORMAT;
534 AssertCompileMembersSameSizeAndOffset(mach_header_64_t, sizeofcmds, mach_header_32_t, sizeofcmds);
535 if (uBuf.Hdr32.sizeofcmds > X86_PAGE_4K_SIZE * 2 - sizeof(mach_header_64_t))
536 return VERR_BAD_EXE_FORMAT;
537
538 /* Do we need to read a 2nd page to get all the load commands? If so, do it. */
539 if (uBuf.Hdr32.sizeofcmds + (f64Bit ? sizeof(mach_header_64_t) : sizeof(mach_header_32_t)) > X86_PAGE_4K_SIZE)
540 {
541 rc = DBGFR3MemRead(pUVM, 0 /*idCpu*/, DBGFR3AddrFromFlat(pUVM, &ModAddr, uModAddr + X86_PAGE_4K_SIZE),
542 &uBuf.ab[X86_PAGE_4K_SIZE], X86_PAGE_4K_SIZE);
543 if (RT_FAILURE(rc))
544 return rc;
545 }
546
547 /*
548 * Process the load commands.
549 */
550 RTDBGSEGMENT aSegs[24];
551 uint32_t cSegs = 0;
552 RTUUID Uuid = RTUUID_INITIALIZE_NULL;
553 uint32_t cLeft = uBuf.Hdr32.ncmds;
554 uint32_t cbLeft = uBuf.Hdr32.sizeofcmds;
555 union
556 {
557 uint8_t const *pb;
558 load_command_t const *pGenric;
559 segment_command_32_t const *pSeg32;
560 segment_command_64_t const *pSeg64;
561 section_32_t const *pSect32;
562 section_64_t const *pSect64;
563 symtab_command_t const *pSymTab;
564 uuid_command_t const *pUuid;
565 } uLCmd;
566 uLCmd.pb = &uBuf.ab[f64Bit ? sizeof(mach_header_64_t) : sizeof(mach_header_32_t)];
567
568 while (cLeft-- > 0)
569 {
570 uint32_t const cbCmd = uLCmd.pGenric->cmdsize;
571 if (cbCmd > cbLeft || cbCmd < sizeof(load_command_t))
572 return VERR_BAD_EXE_FORMAT;
573
574 switch (uLCmd.pGenric->cmd)
575 {
576 case LC_SEGMENT_32:
577 if (cbCmd != sizeof(segment_command_32_t) + uLCmd.pSeg32->nsects * sizeof(section_32_t))
578 return VERR_BAD_EXE_FORMAT;
579 if (!dbgDiggerDarwinIsValidSegOrSectName(uLCmd.pSeg32->segname, sizeof(uLCmd.pSeg32->segname)))
580 return VERR_INVALID_NAME;
581 if ( !strcmp(uLCmd.pSeg32->segname, "__LINKEDIT")
582 && !dbgDiggerDarwinIsSegmentPresent(pUVM, uLCmd.pSeg32->vmaddr, uLCmd.pSeg32->vmsize,
583 uModAddr, uModAddr + _64M))
584 break; /* This usually is discarded or not loaded at all. */
585 if (cSegs >= RT_ELEMENTS(aSegs))
586 return VERR_BUFFER_OVERFLOW;
587 aSegs[cSegs].Address = uLCmd.pSeg32->vmaddr;
588 aSegs[cSegs].uRva = uLCmd.pSeg32->vmaddr - uModAddr;
589 aSegs[cSegs].cb = uLCmd.pSeg32->vmsize;
590 aSegs[cSegs].fFlags = uLCmd.pSeg32->flags; /* Abusing the flags field here... */
591 aSegs[cSegs].iSeg = cSegs;
592 AssertCompile(RTDBG_SEGMENT_NAME_LENGTH > sizeof(uLCmd.pSeg32->segname));
593 strcpy(aSegs[cSegs].szName, uLCmd.pSeg32->segname);
594 cSegs++;
595 break;
596
597 case LC_SEGMENT_64:
598 if (cbCmd != sizeof(segment_command_64_t) + uLCmd.pSeg64->nsects * sizeof(section_64_t))
599 return VERR_BAD_EXE_FORMAT;
600 if (!dbgDiggerDarwinIsValidSegOrSectName(uLCmd.pSeg64->segname, sizeof(uLCmd.pSeg64->segname)))
601 return VERR_INVALID_NAME;
602 if ( !strcmp(uLCmd.pSeg64->segname, "__LINKEDIT")
603 && !dbgDiggerDarwinIsSegmentPresent(pUVM, uLCmd.pSeg64->vmaddr, uLCmd.pSeg64->vmsize,
604 uModAddr, uModAddr + _128M))
605 break; /* This usually is discarded or not loaded at all. */
606 if (cSegs >= RT_ELEMENTS(aSegs))
607 return VERR_BUFFER_OVERFLOW;
608 aSegs[cSegs].Address = uLCmd.pSeg64->vmaddr;
609 aSegs[cSegs].uRva = uLCmd.pSeg64->vmaddr - uModAddr;
610 aSegs[cSegs].cb = uLCmd.pSeg64->vmsize;
611 aSegs[cSegs].fFlags = uLCmd.pSeg64->flags; /* Abusing the flags field here... */
612 aSegs[cSegs].iSeg = cSegs;
613 AssertCompile(RTDBG_SEGMENT_NAME_LENGTH > sizeof(uLCmd.pSeg64->segname));
614 strcpy(aSegs[cSegs].szName, uLCmd.pSeg64->segname);
615 cSegs++;
616 break;
617
618 case LC_UUID:
619 if (cbCmd != sizeof(uuid_command_t))
620 return VERR_BAD_EXE_FORMAT;
621 if (RTUuidIsNull((PCRTUUID)&uLCmd.pUuid->uuid[0]))
622 return VERR_BAD_EXE_FORMAT;
623 memcpy(&Uuid, &uLCmd.pUuid->uuid[0], sizeof(uLCmd.pUuid->uuid));
624 break;
625
626 default:
627 /* Current known max plus a lot of slack. */
628 if (uLCmd.pGenric->cmd > LC_DYLIB_CODE_SIGN_DRS + 32)
629 return VERR_BAD_EXE_FORMAT;
630 break;
631 }
632
633 /* next */
634 cbLeft -= cbCmd;
635 uLCmd.pb += cbCmd;
636 }
637
638 if (cbLeft != 0)
639 return VERR_BAD_EXE_FORMAT;
640
641 /*
642 * Some post processing checks.
643 */
644 uint32_t iSeg;
645 for (iSeg = 0; iSeg < cSegs; iSeg++)
646 if (aSegs[iSeg].Address == uModAddr)
647 break;
648 if (iSeg >= cSegs)
649 {
650 LogRel2(("OSXDig: uModAddr=%#RX64 was not found among the segments segments\n", uModAddr));
651 return VERR_ADDRESS_CONFLICT;
652 }
653
654 /*
655 * Create a debug module.
656 */
657 RTDBGMOD hMod;
658 rc = RTDbgModCreateFromMachOImage(&hMod, pszName, NULL, f64Bit ? RTLDRARCH_AMD64 : RTLDRARCH_X86_32, 0 /*cbImage*/,
659 cSegs, aSegs, &Uuid, DBGFR3AsGetConfig(pUVM), RTDBGMOD_F_NOT_DEFERRED);
660
661 if (RT_FAILURE(rc))
662 {
663 /** @todo try open in memory. */
664
665 /*
666 * Final fallback is a container module.
667 */
668 rc = RTDbgModCreate(&hMod, pszName, 0, 0);
669 if (RT_FAILURE(rc))
670 return rc;
671
672 uint64_t uRvaNext = 0;
673 for (iSeg = 0; iSeg < cSegs && RT_SUCCESS(rc); iSeg++)
674 {
675 if ( aSegs[iSeg].uRva > uRvaNext
676 && aSegs[iSeg].uRva - uRvaNext < _1M)
677 uRvaNext = aSegs[iSeg].uRva;
678 rc = RTDbgModSegmentAdd(hMod, aSegs[iSeg].uRva, aSegs[iSeg].cb, aSegs[iSeg].szName, 0, NULL);
679 if (aSegs[iSeg].cb > 0 && RT_SUCCESS(rc))
680 {
681 char szTmp[RTDBG_SEGMENT_NAME_LENGTH + sizeof("_start")];
682 strcat(strcpy(szTmp, aSegs[iSeg].szName), "_start");
683 rc = RTDbgModSymbolAdd(hMod, szTmp, iSeg, 0 /*uRva*/, 0 /*cb*/, 0 /*fFlags*/, NULL);
684 }
685 uRvaNext += aSegs[iSeg].cb;
686 }
687
688 if (RT_FAILURE(rc))
689 {
690 RTDbgModRelease(hMod);
691 return rc;
692 }
693 }
694
695 /* Tag the module. */
696 rc = RTDbgModSetTag(hMod, DIG_DARWIN_MOD_TAG);
697 AssertRC(rc);
698
699 /*
700 * Link the module.
701 */
702 RTDBGAS hAs = DBGFR3AsResolveAndRetain(pUVM, DBGF_AS_KERNEL);
703 if (hAs != NIL_RTDBGAS)
704 {
705 //uint64_t uRvaNext = 0; - what was this?
706 uint32_t cLinked = 0;
707 iSeg = cSegs;
708 while (iSeg-- > 0) /* HACK: Map in reverse order to avoid replacing __TEXT. */
709 if (aSegs[iSeg].cb)
710 {
711 /* Find matching segment in the debug module. */
712 uint32_t iDbgSeg = 0;
713 while (iDbgSeg < cSegs)
714 {
715 RTDBGSEGMENT SegInfo;
716 int rc3 = RTDbgModSegmentByIndex(hMod, iDbgSeg, &SegInfo);
717 if (RT_SUCCESS(rc3) && !strcmp(SegInfo.szName, aSegs[iSeg].szName))
718 break;
719 iDbgSeg++;
720 }
721 AssertMsgStmt(iDbgSeg < cSegs, ("%s\n", aSegs[iSeg].szName), continue);
722
723 /* Map it. */
724 int rc2 = RTDbgAsModuleLinkSeg(hAs, hMod, iDbgSeg, aSegs[iSeg].Address, RTDBGASLINK_FLAGS_REPLACE /*fFlags*/);
725 if (RT_SUCCESS(rc2))
726 cLinked++;
727 else if (RT_SUCCESS(rc))
728 rc = rc2;
729 }
730 if (RT_FAILURE(rc) && cLinked != 0)
731 rc = -rc;
732 }
733 else
734 rc = VERR_INTERNAL_ERROR;
735
736 RTDbgModRelease(hMod);
737 RTDbgAsRelease(hAs);
738
739 if (pf64Bit)
740 *pf64Bit = f64Bit;
741 return rc;
742}
743
744
745static bool dbgDiggerDarwinIsValidName(const char *pszName)
746{
747 char ch;
748 while ((ch = *pszName++) != '\0')
749 {
750 if (ch < 0x20 || ch >= 127)
751 return false;
752 }
753 return true;
754}
755
756
757static bool dbgDiggerDarwinIsValidVersion(const char *pszVersion)
758{
759 char ch;
760 while ((ch = *pszVersion++) != '\0')
761 {
762 if (ch < 0x20 || ch >= 127)
763 return false;
764 }
765 return true;
766}
767
768
769/**
770 * @copydoc DBGFOSREG::pfnInit
771 */
772static DECLCALLBACK(int) dbgDiggerDarwinInit(PUVM pUVM, void *pvData)
773{
774 PDBGDIGGERDARWIN pThis = (PDBGDIGGERDARWIN)pvData;
775 Assert(!pThis->fValid);
776
777 /*
778 * Add the kernel module.
779 */
780 bool f64Bit;
781 int rc = dbgDiggerDarwinAddModule(pThis, pUVM, pThis->AddrKernel.FlatPtr, "mach_kernel", &f64Bit);
782 if (RT_SUCCESS(rc))
783 {
784 /*
785 * The list of modules can be found at the 'kmod' symbol, that means
786 * that we currently require some kind of symbol file for the kernel
787 * to be loaded at this point.
788 *
789 * Note! Could also use the 'gLoadedKextSummaries', but I don't think
790 * it's any easier to find without any kernel map than 'kmod'.
791 */
792 RTDBGSYMBOL SymInfo;
793 rc = DBGFR3AsSymbolByName(pUVM, DBGF_AS_KERNEL, "mach_kernel!kmod", &SymInfo, NULL);
794 if (RT_FAILURE(rc))
795 rc = DBGFR3AsSymbolByName(pUVM, DBGF_AS_KERNEL, "mach_kernel!_kmod", &SymInfo, NULL);
796 if (RT_SUCCESS(rc))
797 {
798 DBGFADDRESS AddrModInfo;
799 DBGFR3AddrFromFlat(pUVM, &AddrModInfo, SymInfo.Value);
800
801 /* Read the variable. */
802 RTUINT64U uKmodValue = { 0 };
803 if (f64Bit)
804 rc = DBGFR3MemRead(pUVM, 0 /*idCpu*/, &AddrModInfo, &uKmodValue.u, sizeof(uKmodValue.u));
805 else
806 rc = DBGFR3MemRead (pUVM, 0 /*idCpu*/, &AddrModInfo, &uKmodValue.s.Lo, sizeof(uKmodValue.s.Lo));
807 if (RT_SUCCESS(rc))
808 {
809 DBGFR3AddrFromFlat(pUVM, &AddrModInfo, uKmodValue.u);
810
811 /* Walk the list of modules. */
812 uint32_t cIterations = 0;
813 while (AddrModInfo.FlatPtr != 0)
814 {
815 /* Some extra loop conditions... */
816 if (!OSX_VALID_ADDRESS(f64Bit, AddrModInfo.FlatPtr))
817 {
818 LogRel(("OSXDig: Invalid kmod_info pointer: %RGv\n", AddrModInfo.FlatPtr));
819 break;
820 }
821 if (AddrModInfo.FlatPtr == uKmodValue.u && cIterations != 0)
822 {
823 LogRel(("OSXDig: kmod_info list looped back to the start.\n"));
824 break;
825 }
826 if (cIterations++ >= 2048)
827 {
828 LogRel(("OSXDig: Too many mod_info loops (%u)\n", cIterations));
829 break;
830 }
831
832 /*
833 * Read the kmod_info_t structure.
834 */
835 union
836 {
837 OSX64_kmod_info_t Info64;
838 OSX32_kmod_info_t Info32;
839 } uMod;
840 RT_ZERO(uMod);
841 rc = DBGFR3MemRead(pUVM, 0 /*idCpu*/, &AddrModInfo, &uMod,
842 f64Bit ? sizeof(uMod.Info64) : sizeof(uMod.Info32));
843 if (RT_FAILURE(rc))
844 {
845 LogRel(("OSXDig: Error reading kmod_info structure at %RGv: %Rrc\n", AddrModInfo.FlatPtr, rc));
846 break;
847 }
848
849 /*
850 * Validate the kmod_info_t structure.
851 */
852 int32_t iInfoVer = f64Bit ? uMod.Info64.info_version : uMod.Info32.info_version;
853 if (iInfoVer != OSX_KMOD_INFO_VERSION)
854 {
855 LogRel(("OSXDig: kmod_info @%RGv: Bad info_version %d\n", AddrModInfo.FlatPtr, iInfoVer));
856 break;
857 }
858
859 const char *pszName = f64Bit ? uMod.Info64.name : uMod.Info32.name;
860 if ( !*pszName
861 || !RTStrEnd(pszName, sizeof(uMod.Info64.name))
862 || !dbgDiggerDarwinIsValidName(pszName) )
863 {
864 LogRel(("OSXDig: kmod_info @%RGv: Bad name '%.*s'\n", AddrModInfo.FlatPtr,
865 sizeof(uMod.Info64.name), pszName));
866 break;
867 }
868
869 const char *pszVersion = f64Bit ? uMod.Info64.version : uMod.Info32.version;
870 if ( !RTStrEnd(pszVersion, sizeof(uMod.Info64.version))
871 || !dbgDiggerDarwinIsValidVersion(pszVersion) )
872 {
873 LogRel(("OSXDig: kmod_info @%RGv: Bad version '%.*s'\n", AddrModInfo.FlatPtr,
874 sizeof(uMod.Info64.version), pszVersion));
875 break;
876 }
877
878 int32_t cRefs = f64Bit ? uMod.Info64.reference_count : uMod.Info32.reference_count;
879 if (cRefs < -1 || cRefs > 16384)
880 {
881 LogRel(("OSXDig: kmod_info @%RGv: Bad reference_count %d\n", AddrModInfo.FlatPtr, cRefs));
882 break;
883 }
884
885 uint64_t uImageAddr = f64Bit ? uMod.Info64.address : uMod.Info32.address;
886 if (!OSX_VALID_ADDRESS(f64Bit, uImageAddr))
887 {
888 LogRel(("OSXDig: kmod_info @%RGv: Bad address %#llx\n", AddrModInfo.FlatPtr, uImageAddr));
889 break;
890 }
891
892 uint64_t cbImage = f64Bit ? uMod.Info64.size : uMod.Info32.size;
893 if (cbImage > 64U*_1M)
894 {
895 LogRel(("OSXDig: kmod_info @%RGv: Bad size %#llx\n", AddrModInfo.FlatPtr, cbImage));
896 break;
897 }
898
899 uint64_t cbHdr = f64Bit ? uMod.Info64.hdr_size : uMod.Info32.hdr_size;
900 if (cbHdr > 16U*_1M)
901 {
902 LogRel(("OSXDig: kmod_info @%RGv: Bad hdr_size %#llx\n", AddrModInfo.FlatPtr, cbHdr));
903 break;
904 }
905
906 uint64_t uStartAddr = f64Bit ? uMod.Info64.start : uMod.Info32.start;
907 if (!uStartAddr && !OSX_VALID_ADDRESS(f64Bit, uStartAddr))
908 {
909 LogRel(("OSXDig: kmod_info @%RGv: Bad start function %#llx\n", AddrModInfo.FlatPtr, uStartAddr));
910 break;
911 }
912
913 uint64_t uStopAddr = f64Bit ? uMod.Info64.stop : uMod.Info32.stop;
914 if (!uStopAddr && !OSX_VALID_ADDRESS(f64Bit, uStopAddr))
915 {
916 LogRel(("OSXDig: kmod_info @%RGv: Bad stop function %#llx\n", AddrModInfo.FlatPtr, uStopAddr));
917 break;
918 }
919
920 /*
921 * Try add the module.
922 */
923 LogRel(("OSXDig: kmod_info @%RGv: '%s' ver '%s', image @%#llx LB %#llx cbHdr=%#llx\n", AddrModInfo.FlatPtr,
924 pszName, pszVersion, uImageAddr, cbImage, cbHdr));
925 rc = dbgDiggerDarwinAddModule(pThis, pUVM, uImageAddr, pszName, NULL);
926
927
928 /*
929 * Advance to the next kmod_info entry.
930 */
931 DBGFR3AddrFromFlat(pUVM, &AddrModInfo, f64Bit ? uMod.Info64.next : uMod.Info32.next);
932 }
933 }
934 else
935 LogRel(("OSXDig: Error reading the 'kmod' variable: %Rrc\n", rc));
936 }
937 else
938 LogRel(("OSXDig: Failed to locate the 'kmod' variable in mach_kernel.\n"));
939
940 pThis->fValid = true;
941 return VINF_SUCCESS;
942 }
943
944 return rc;
945}
946
947
948/**
949 * @copydoc DBGFOSREG::pfnProbe
950 */
951static DECLCALLBACK(bool) dbgDiggerDarwinProbe(PUVM pUVM, void *pvData)
952{
953 PDBGDIGGERDARWIN pThis = (PDBGDIGGERDARWIN)pvData;
954
955 /*
956 * Look for a section + segment combo that normally only occures in
957 * mach_kernel. Follow it up with probing of the rest of the executable
958 * header. We must search a largish area because the more recent versions
959 * of darwin have random load address for security raisins.
960 */
961 static struct { uint64_t uStart, uEnd; } const s_aRanges[] =
962 {
963 /* 64-bit: */
964 { UINT64_C(0xffffff8000000000), UINT64_C(0xffffff81ffffffff), },
965
966 /* 32-bit - always search for this because of the hybrid 32-bit kernel
967 with cpu in long mode that darwin used for a number of versions. */
968 { UINT64_C(0x00001000), UINT64_C(0x0ffff000), }
969 };
970 for (unsigned iRange = DBGFR3CpuGetMode(pUVM, 0 /*idCpu*/) != CPUMMODE_LONG;
971 iRange < RT_ELEMENTS(s_aRanges);
972 iRange++)
973 {
974 DBGFADDRESS KernelAddr;
975 for (DBGFR3AddrFromFlat(pUVM, &KernelAddr, s_aRanges[iRange].uStart);
976 KernelAddr.FlatPtr < s_aRanges[iRange].uEnd;
977 KernelAddr.FlatPtr += X86_PAGE_4K_SIZE)
978 {
979 static const uint8_t s_abNeedle[16 + 16] =
980 {
981 '_','_','t','e','x','t', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* section_32_t::sectname */
982 '_','_','K','L','D', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* section_32_t::segname. */
983 };
984
985 int rc = DBGFR3MemScan(pUVM, 0 /*idCpu*/, &KernelAddr, s_aRanges[iRange].uEnd - KernelAddr.FlatPtr,
986 1, s_abNeedle, sizeof(s_abNeedle), &KernelAddr);
987 if (RT_FAILURE(rc))
988 break;
989 DBGFR3AddrSub(&KernelAddr, KernelAddr.FlatPtr & X86_PAGE_4K_OFFSET_MASK);
990
991 /*
992 * Read the first page of the image and check the headers.
993 */
994 union
995 {
996 uint8_t ab[X86_PAGE_4K_SIZE];
997 mach_header_64_t Hdr64;
998 mach_header_32_t Hdr32;
999 } uBuf;
1000 rc = DBGFR3MemRead(pUVM, 0 /*idCpu*/, &KernelAddr, uBuf.ab, X86_PAGE_4K_SIZE);
1001 if (RT_FAILURE(rc))
1002 continue;
1003 AssertCompileMembersSameSizeAndOffset(mach_header_64_t, magic, mach_header_32_t, magic);
1004 if ( uBuf.Hdr64.magic != IMAGE_MACHO64_SIGNATURE
1005 && uBuf.Hdr32.magic != IMAGE_MACHO32_SIGNATURE)
1006 continue;
1007 AssertCompileMembersSameSizeAndOffset(mach_header_64_t, cputype, mach_header_32_t, cputype);
1008 bool f64Bit = uBuf.Hdr64.magic == IMAGE_MACHO64_SIGNATURE;
1009 if (uBuf.Hdr32.cputype != (f64Bit ? CPU_TYPE_X86_64 : CPU_TYPE_I386))
1010 continue;
1011 AssertCompileMembersSameSizeAndOffset(mach_header_64_t, filetype, mach_header_32_t, filetype);
1012 if (uBuf.Hdr32.filetype != MH_EXECUTE)
1013 continue;
1014 AssertCompileMembersSameSizeAndOffset(mach_header_64_t, ncmds, mach_header_32_t, ncmds);
1015 if (uBuf.Hdr32.ncmds > 256)
1016 continue;
1017 AssertCompileMembersSameSizeAndOffset(mach_header_64_t, sizeofcmds, mach_header_32_t, sizeofcmds);
1018 if (uBuf.Hdr32.sizeofcmds > X86_PAGE_4K_SIZE * 2 - sizeof(mach_header_64_t))
1019 continue;
1020
1021 /* Seems good enough for now.
1022
1023 If the above causes false positives, check the segments and make
1024 sure there is a kernel version string in the right one. */
1025 pThis->AddrKernel = KernelAddr;
1026 pThis->f64Bit = f64Bit;
1027
1028 /*
1029 * Finally, find the kernel version string.
1030 */
1031 rc = DBGFR3MemScan(pUVM, 0 /*idCpu*/, &KernelAddr, 32*_1M, 1, RT_STR_TUPLE("Darwin Kernel Version"),
1032 &pThis->AddrKernelVersion);
1033 if (RT_FAILURE(rc))
1034 DBGFR3AddrFromFlat(pUVM, &pThis->AddrKernelVersion, 0);
1035 return true;
1036 }
1037 }
1038 return false;
1039}
1040
1041
1042/**
1043 * @copydoc DBGFOSREG::pfnDestruct
1044 */
1045static DECLCALLBACK(void) dbgDiggerDarwinDestruct(PUVM pUVM, void *pvData)
1046{
1047 RT_NOREF2(pUVM, pvData);
1048
1049}
1050
1051
1052/**
1053 * @copydoc DBGFOSREG::pfnConstruct
1054 */
1055static DECLCALLBACK(int) dbgDiggerDarwinConstruct(PUVM pUVM, void *pvData)
1056{
1057 RT_NOREF1(pUVM);
1058 PDBGDIGGERDARWIN pThis = (PDBGDIGGERDARWIN)pvData;
1059
1060 pThis->IDmesg.u32Magic = DBGFOSIDMESG_MAGIC;
1061 pThis->IDmesg.pfnQueryKernelLog = dbgDiggerDarwinIDmsg_QueryKernelLog;
1062 pThis->IDmesg.u32EndMagic = DBGFOSIDMESG_MAGIC;
1063
1064 return VINF_SUCCESS;
1065}
1066
1067
1068const DBGFOSREG g_DBGDiggerDarwin =
1069{
1070 /* .u32Magic = */ DBGFOSREG_MAGIC,
1071 /* .fFlags = */ 0,
1072 /* .cbData = */ sizeof(DBGDIGGERDARWIN),
1073 /* .szName = */ "Darwin",
1074 /* .pfnConstruct = */ dbgDiggerDarwinConstruct,
1075 /* .pfnDestruct = */ dbgDiggerDarwinDestruct,
1076 /* .pfnProbe = */ dbgDiggerDarwinProbe,
1077 /* .pfnInit = */ dbgDiggerDarwinInit,
1078 /* .pfnRefresh = */ dbgDiggerDarwinRefresh,
1079 /* .pfnTerm = */ dbgDiggerDarwinTerm,
1080 /* .pfnQueryVersion = */ dbgDiggerDarwinQueryVersion,
1081 /* .pfnQueryInterface = */ dbgDiggerDarwinQueryInterface,
1082 /* .pfnStackUnwindAssist = */ dbgDiggerDarwinStackUnwindAssist,
1083 /* .u32EndMagic = */ DBGFOSREG_MAGIC
1084};
1085
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette