diff --git a/libctru/include/3ds/errf.h b/libctru/include/3ds/errf.h index 70123a9..f1acd92 100644 --- a/libctru/include/3ds/errf.h +++ b/libctru/include/3ds/errf.h @@ -7,11 +7,6 @@ #include <3ds/types.h> -/// Used for register dumps. -typedef struct { - u32 r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12, r13, r14, r15, cpsr; -} ERRF_ExceptionContext; - /// Types of errors that can be thrown by err:f. typedef enum { ERRF_ERRTYPE_GENERIC = 0, ///< For generic errors. Shows miscellaneous info. @@ -19,7 +14,7 @@ typedef enum { ERRF_ERRTYPE_CARD_REMOVED = 2, ///< Displays the "The Game Card was removed." message. ERRF_ERRTYPE_EXCEPTION = 3, ///< For exceptions, or more specifically 'crashes'. union data should be exception_data. ERRF_ERRTYPE_FAILURE = 4, ///< For general failure. Shows a message. union data should have a string set in failure_mesg - ERRF_ERRTYPE_LOGGED = 5 ///< Outputs logs to NAND in some cases. + ERRF_ERRTYPE_LOGGED = 5, ///< Outputs logs to NAND in some cases. } ERRF_ErrType; /// Types of 'Exceptions' thrown for ERRF_ERRTYPE_EXCEPTION @@ -27,7 +22,7 @@ typedef enum { ERRF_EXCEPTION_PREFETCH_ABORT = 0, ///< Prefetch Abort ERRF_EXCEPTION_DATA_ABORT = 1, ///< Data abort ERRF_EXCEPTION_UNDEFINED = 2, ///< Undefined instruction - ERRF_EXCEPTION_VFP = 3 ///< VFP (floating point) exception. + ERRF_EXCEPTION_VFP = 3, ///< VFP (floating point) exception. } ERRF_ExceptionType; typedef struct { @@ -42,7 +37,7 @@ typedef struct { typedef struct { ERRF_ExceptionInfo excep; ///< Exception info struct - ERRF_ExceptionContext regs; ///< Register dump. + CpuRegisters regs; ///< CPU register dump. u8 pad[4]; } ERRF_ExceptionData; diff --git a/libctru/include/3ds/svc.h b/libctru/include/3ds/svc.h index 7b2a524..bf1d708 100644 --- a/libctru/include/3ds/svc.h +++ b/libctru/include/3ds/svc.h @@ -49,15 +49,15 @@ typedef enum { MEMSTATE_ALIASED = 8, ///< Aliased memory MEMSTATE_ALIAS = 9, ///< Alias memory MEMSTATE_ALIASCODE = 10, ///< Aliased code memory - MEMSTATE_LOCKED = 11 ///< Locked memory + MEMSTATE_LOCKED = 11, ///< Locked memory } MemState; /// Memory permission flags typedef enum { - MEMPERM_READ = 1, ///< Readable - MEMPERM_WRITE = 2, ///< Writable - MEMPERM_EXECUTE = 4, ///< Executable - MEMPERM_DONTCARE = 0x10000000 ///< Don't care + MEMPERM_READ = 1, ///< Readable + MEMPERM_WRITE = 2, ///< Writable + MEMPERM_EXECUTE = 4, ///< Executable + MEMPERM_DONTCARE = 0x10000000, ///< Don't care } MemPerm; /// Memory information. @@ -103,7 +103,7 @@ typedef enum { } ThreadInfoType; /// Pseudo handle for the current thread -#define CUR_THREAD_HANDLE 0xFFFF8000 +#define CUR_THREAD_HANDLE 0xFFFF8000 ///@} @@ -111,83 +111,114 @@ typedef enum { ///@name Debugging ///@{ -/// Reasons for a process event. -typedef enum { - REASON_CREATE = 1, ///< Process created. - REASON_ATTACH = 2 ///< Process attached. -} ProcessEventReason; - -/// Event relating to a process. +/// Event relating to the attachment of a process. typedef struct { - u64 program_id; ///< ID of the program. - u8 process_name[8]; ///< Name of the process. - u32 process_id; ///< ID of the process. - u32 reason; ///< Reason for the event. See @ref ProcessEventReason -} ProcessEvent; + u64 program_id; ///< ID of the program. + char process_name[8]; ///< Name of the process. + u32 process_id; ///< ID of the process. + u32 other_flags; ///< Always 0 +} AttachProcessEvent; /// Reasons for an exit process event. typedef enum { EXITPROCESS_EVENT_NONE = 0, ///< No reason. EXITPROCESS_EVENT_TERMINATE = 1, ///< Process terminated. - EXITPROCESS_EVENT_UNHANDLED_EXCEPTION = 2 ///< Unhandled exception occurred. + EXITPROCESS_EVENT_UNHANDLED_EXCEPTION = 2, ///< Unhandled exception occurred. } ExitProcessEventReason; /// Event relating to the exiting of a process. typedef struct { - u32 reason; ///< Reason for exiting. See @ref ExitProcessEventReason + ExitProcessEventReason reason; ///< Reason for exiting. See @ref ExitProcessEventReason } ExitProcessEvent; -/// Event relating to the creation of a thread. +/// Event relating to the attachment of a thread. typedef struct { - u32 creator_thread_id; ///< ID of the creating thread. - u32 base_addr; ///< Base address. - u32 entry_point; ///< Entry point of the thread. -} CreateThreadEvent; + u32 creator_thread_id; ///< ID of the creating thread. + u32 thread_local_storage; ///< Thread local storage. + u32 entry_point; ///< Entry point of the thread. +} AttachThreadEvent; /// Reasons for an exit thread event. typedef enum { EXITTHREAD_EVENT_NONE = 0, ///< No reason. EXITTHREAD_EVENT_TERMINATE = 1, ///< Thread terminated. EXITTHREAD_EVENT_UNHANDLED_EXC = 2, ///< Unhandled exception occurred. - EXITTHREAD_EVENT_TERMINATE_PROCESS = 3 ///< Process terminated. + EXITTHREAD_EVENT_TERMINATE_PROCESS = 3, ///< Process terminated. } ExitThreadEventReason; /// Event relating to the exiting of a thread. typedef struct { - u32 reason; ///< Reason for exiting. See @ref ExitThreadEventReason + ExitThreadEventReason reason; ///< Reason for exiting. See @ref ExitThreadEventReason } ExitThreadEvent; /// Reasons for a user break. typedef enum { - USERBREAK_PANIC = 0, ///< Panic. - USERBREAK_ASSERT = 1, ///< Assertion failed. - USERBREAK_USER = 2 ///< User related. + USERBREAK_PANIC = 0, ///< Panic. + USERBREAK_ASSERT = 1, ///< Assertion failed. + USERBREAK_USER = 2, ///< User related. + USERBREAK_LOAD_RO = 3, ///< Load RO. + USERBREAK_UNLOAD_RO = 4, ///< Unload RO. } UserBreakType; /// Reasons for an exception event. typedef enum { - EXC_EVENT_UNDEFINED_INSTRUCTION = 0, ///< Undefined instruction. arg: (None) - EXC_EVENT_UNKNOWN1 = 1, ///< Unknown. arg: (None) - EXC_EVENT_UNKNOWN2 = 2, ///< Unknown. arg: address - EXC_EVENT_UNKNOWN3 = 3, ///< Unknown. arg: address - EXC_EVENT_ATTACH_BREAK = 4, ///< Attached break. arg: (None) - EXC_EVENT_BREAKPOINT = 5, ///< Breakpoint reached. arg: (None) - EXC_EVENT_USER_BREAK = 6, ///< User break occurred. arg: @ref UserBreakType - EXC_EVENT_DEBUGGER_BREAK = 7, ///< Debugger break occurred. arg: (None) - EXC_EVENT_UNDEFINED_SYSCALL = 8 ///< Undefined syscall. arg: attempted syscall + EXCEVENT_UNDEFINED_INSTRUCTION = 0, ///< Undefined instruction. + EXCEVENT_PREFETCH_ABORT = 1, ///< Prefetch abort. + EXCEVENT_DATA_ABORT = 2, ///< Data abort (other than the below kind). + EXCEVENT_UNALIGNED_DATA_ACCESS = 3, ///< Unaligned data access. + EXCEVENT_ATTACH_BREAK = 4, ///< Attached break. + EXCEVENT_STOP_POINT = 5, ///< Stop point reached. + EXCEVENT_USER_BREAK = 6, ///< User break occurred. + EXCEVENT_DEBUGGER_BREAK = 7, ///< Debugger break occurred. + EXCEVENT_UNDEFINED_SYSCALL = 8, ///< Undefined syscall. } ExceptionEventType; +/// Event relating to fault exceptions (CPU exceptions other than stop points and undefined syscalls). +typedef struct { + u32 fault_information; ///< FAR (for DATA ABORT / UNALIGNED DATA ACCESS), attempted syscall or 0 +} FaultExceptionEvent; + +/// Stop point types +typedef enum { + STOPPOINT_SVC_FF = 0, ///< See @ref SVC_STOP_POINT. + STOPPOINT_BREAKPOINT = 1, ///< Breakpoint. + STOPPOINT_WATCHPOINT = 2, ///< Watchpoint. +} StopPointType; + +/// Event relating to stop points +typedef struct { + StopPointType type; ///< Stop point type, see @ref StopPointType. + u32 fault_information; ///< FAR for Watchpoints, otherwise 0. +} StopPointExceptionEvent; + +/// Event relating to @ref svcBreak +typedef struct { + UserBreakType type; ///< User break type, see @ref UserBreakType. + u32 croInfo; ///< For LOAD_RO and UNLOAD_RO. + u32 croInfoSize; ///< For LOAD_RO and UNLOAD_RO. +} UserBreakExceptionEvent; + +/// Event relating to @ref svcBreakDebugProcess +typedef struct { + void *threads[4]; ///< KThread instances of the attached process's that were running on each at the time of the function call (only the first 2 values are meaningful on O3DS). +} DebuggerBreakExceptionEvent; + /// Event relating to exceptions. typedef struct { - u32 type; ///< Type of event. See @ref ExceptionEventType - u32 address; ///< Address of the exception. - u32 argument; ///< Event argument. See @ref ExceptionEventType + ExceptionEventType type; ///< Type of event. See @ref ExceptionEventType. + u32 address; ///< Address of the exception. + union { + FaultExceptionEvent fault; ///< Fault exception event data. + StopPointExceptionEvent stop_point; ///< Stop point exception event data. + UserBreakExceptionEvent user_break; ///< User break exception event data. + DebuggerBreakExceptionEvent debugger_break; ///< Debugger break exception event data + }; } ExceptionEvent; /// Event relating to the scheduler. typedef struct { u64 clock_tick; ///< Clock tick that the event occurred. -} SchedulerInOutEvent; +} ScheduleInOutEvent; /// Event relating to syscalls. typedef struct { @@ -203,49 +234,84 @@ typedef struct { /// Event relating to the mapping of memory. typedef struct { - u32 mapped_addr; ///< Mapped address. - u32 mapped_size; ///< Mapped size. - u32 memperm; ///< Memory permissions. See @ref MemPerm - u32 memstate; ///< Memory state. See @ref MemState + u32 mapped_addr; ///< Mapped address. + u32 mapped_size; ///< Mapped size. + MemPerm memperm; ///< Memory permissions. See @ref MemPerm. + MemState memstate; ///< Memory state. See @ref MemState. } MapEvent; /// Debug event type. typedef enum { - DBG_EVENT_PROCESS = 0, ///< Process event. - DBG_EVENT_CREATE_THREAD = 1, ///< Thread creation event. - DBG_EVENT_EXIT_THREAD = 2, ///< Thread exit event. - DBG_EVENT_EXIT_PROCESS = 3, ///< Process exit event. - DBG_EVENT_EXCEPTION = 4, ///< Exception event. - DBG_EVENT_DLL_LOAD = 5, ///< DLL load event. - DBG_EVENT_DLL_UNLOAD = 6, ///< DLL unload event. - DBG_EVENT_SCHEDULE_IN = 7, ///< Schedule in event. - DBG_EVENT_SCHEDULE_OUT = 8, ///< Schedule out event. - DBG_EVENT_SYSCALL_IN = 9, ///< Syscall in event. - DBG_EVENT_SYSCALL_OUT = 10, ///< Syscall out event. - DBG_EVENT_OUTPUT_STRING = 11, ///< Output string event. - DBG_EVENT_MAP = 12 ///< Map event. + DBGEVENT_ATTACH_PROCESS = 0, ///< Process attached event. + DBGEVENT_ATTACH_THREAD = 1, ///< Thread attached event. + DBGEVENT_EXIT_THREAD = 2, ///< Thread exit event. + DBGEVENT_EXIT_PROCESS = 3, ///< Process exit event. + DBGEVENT_EXCEPTION = 4, ///< Exception event. + DBGEVENT_DLL_LOAD = 5, ///< DLL load event. + DBGEVENT_DLL_UNLOAD = 6, ///< DLL unload event. + DBGEVENT_SCHEDULE_IN = 7, ///< Schedule in event. + DBGEVENT_SCHEDULE_OUT = 8, ///< Schedule out event. + DBGEVENT_SYSCALL_IN = 9, ///< Syscall in event. + DBGEVENT_SYSCALL_OUT = 10, ///< Syscall out event. + DBGEVENT_OUTPUT_STRING = 11, ///< Output string event. + DBGEVENT_MAP = 12, ///< Map event. } DebugEventType; /// Information about a debug event. typedef struct { - u32 type; ///< Type of event. See @ref DebugEventType - u32 thread_id; ///< ID of the thread. - u32 unknown[2]; ///< Unknown data. + DebugEventType type; ///< Type of event. See @ref DebugEventType + u32 thread_id; ///< ID of the thread. + u32 flags; ///< Flags. Bit0 means that @ref svcContinueDebugEvent needs to be called for this event (except for EXIT PROCESS events, where this flag is disregarded). + u8 remnants[4]; ///< Always 0. union { - ProcessEvent process; ///< Process event data. - CreateThreadEvent create_thread; ///< Thread creation event data. - ExitThreadEvent exit_thread; ///< Thread exit event data. - ExitProcessEvent exit_process; ///< Process exit event data. - ExceptionEvent exception; ///< Exception event data. - /* TODO: DLL_LOAD */ - /* TODO: DLL_UNLOAD */ - SchedulerInOutEvent scheduler; ///< Schedule in/out event data. - SyscallInOutEvent syscall; ///< Syscall in/out event data. - OutputStringEvent output_string; ///< Output string event data. - MapEvent map; ///< Map event data. + AttachProcessEvent attach_process; ///< Process attachment event data. + AttachThreadEvent attach_thread; ///< Thread attachment event data. + ExitThreadEvent exit_thread; ///< Thread exit event data. + ExitProcessEvent exit_process; ///< Process exit event data. + ExceptionEvent exception; ///< Exception event data. + /* DLL_LOAD and DLL_UNLOAD do not seem to possess any event data */ + ScheduleInOutEvent scheduler; ///< Schedule in/out event data. + SyscallInOutEvent syscall; ///< Syscall in/out event data. + OutputStringEvent output_string; ///< Output string event data. + MapEvent map; ///< Map event data. }; } DebugEventInfo; +/// Debug flags for an attached process, set by @ref svcContinueDebugEvent +typedef enum { + DBG_NO_ERRF_CPU_EXCEPTION_DUMPS = BIT(0), ///< Don't produce err:f-format dumps for CPU exceptions (including watchpoints and breakpoints, regardless of any @ref svcKernelSetState call). + DBG_SIGNAL_FAULT_EXCEPTION_EVENTS = BIT(1), ///< Signal fault exception events. See @ref FaultExceptionEvent. + DBG_SIGNAL_SCHEDULE_EVENTS = BIT(2), ///< Signal schedule in/out events. See @ref ScheduleInOutEvent. + DBG_SIGNAL_SYSCALL_EVENTS = BIT(3), ///< Signal syscall in/out events. See @ref SyscallInOutEvent. + DBG_SIGNAL_MAP_EVENTS = BIT(4), ///< Signal map events. See @ref MapEvent. +} DebugFlags; + +typedef struct { + CpuRegisters cpu_registers; ///< CPU registers. + FpuRegisters fpu_registers; ///< FPU registers. +} ThreadContext; + +/// Control flags for @ref svcGetDebugThreadContext and @ref svcSetDebugThreadContext +typedef enum { + THREADCONTEXT_CONTROL_CPU_GPRS = BIT(0), ///< Control r0-r12. + THREADCONTEXT_CONTROL_CPU_SPRS = BIT(1), ///< Control sp, lr, pc, cpsr. + THREADCONTEXT_CONTROL_FPU_GPRS = BIT(2), ///< Control d0-d15 (or f0-f31). + THREADCONTEXT_CONTROL_FPU_SPRS = BIT(3), ///< Control fpscr, fpexc. + + THREADCONTEXT_CONTROL_CPU_REGS = BIT(0) | BIT(1), ///< Control r0-r12, sp, lr, pc, cpsr. + THREADCONTEXT_CONTROL_FPU_REGS = BIT(2) | BIT(3), ///< Control d0-d15, fpscr, fpexc. + + THREADCONTEXT_CONTROL_ALL = BIT(0) | BIT(1) | BIT(2) | BIT(3), ///< Control all of the above. +} ThreadContextControlFlags; + +/// Thread parameter field for @ref svcGetDebugThreadParameter +typedef enum { + DBGTHREAD_PARAMETER_PRIORITY = 0, ///< Thread priority. + DBGTHREAD_PARAMETER_SCHEDULING_MASK_LOW = 1, ///< Low scheduling mask. + DBGTHREAD_PARAMETER_CPU_IDEAL = 2, ///< Ideal processor. + DBGTHREAD_PARAMETER_CPU_CREATOR = 3, ///< Processor that created the threod. +} DebugThreadParameter; + ///@} ///@name Processes @@ -451,24 +517,6 @@ Result svcInvalidateProcessDataCache(Handle process, void* addr, u32 size); * @param size Size of the memory to flush. */ Result svcFlushProcessDataCache(Handle process, void const* addr, u32 size); - -/** - * @brief Reads from a process's memory. - * @param buffer Buffer to read data to. - * @param debug Debug handle of the process. - * @param addr Address to read from. - * @param size Size of the memory to read. - */ -Result svcReadProcessMemory(void* buffer, Handle debug, u32 addr, u32 size); - -/** - * @brief Writes to a process's memory. - * @param debug Debug handle of the process. - * @param buffer Buffer to write data from. - * @param addr Address to write to. - * @param size Size of the memory to write. - */ -Result svcWriteProcessMemory(Handle debug, const void* buffer, u32 addr, u32 size); ///@} @@ -962,11 +1010,75 @@ Result svcTerminateDebugProcess(Handle debug); Result svcGetProcessDebugEvent(DebugEventInfo* info, Handle debug); /** - * @brief Continues the current debug event of a debugged process. + * @brief Continues the current debug event of a debugged process (not necessarily the same as @ref svcGetProcessDebugEvent). * @param debug Debug handle of the process. - * @param flags Flags to continue with. + * @param flags Flags to continue with, see @ref DebugFlags. */ -Result svcContinueDebugEvent(Handle debug, u32 flags); +Result svcContinueDebugEvent(Handle debug, DebugFlags flags); + +/** + * @brief Fetches the saved registers of a thread, either inactive or awaiting @ref svcContinueDebugEvent, belonging to a debugged process. + * @param[out] context Values of the registers to fetch, see @ref ThreadContext. + * @param debug Debug handle of the parent process. + * @param threadId ID of the thread to fetch the saved registers of. + * @param controlFlags Which registers to fetch, see @ref ThreadContextControlFlags. + */ +Result svcGetDebugThreadContext(ThreadContext* context, Handle debug, u32 threadId, ThreadContextControlFlags controlFlags); + +/** + * @brief Updates the saved registers of a thread, either inactive or awaiting @ref svcContinueDebugEvent, belonging to a debugged process. + * @param debug Debug handle of the parent process. + * @param threadId ID of the thread to update the saved registers of. + * @param context Values of the registers to update, see @ref ThreadContext. + * @param controlFlags Which registers to update, see @ref ThreadContextControlFlags. + */ +Result svcSetDebugThreadContext(Handle debug, u32 threadId, ThreadContext* context, ThreadContextControlFlags controlFlags); + +/** + * @brief Queries memory information of a debugged process. + * @param[out] info Pointer to output memory info to. + * @param[out] out Pointer to output page info to. + * @param debug Debug handle of the process to query memory from. + * @param addr Virtual memory address to query. + */ +Result svcQueryDebugProcessMemory(MemInfo* info, PageInfo* out, Handle debug, u32 addr); + +/** + * @brief Reads from a debugged process's memory. + * @param buffer Buffer to read data to. + * @param debug Debug handle of the process. + * @param addr Address to read from. + * @param size Size of the memory to read. + */ +Result svcReadProcessMemory(void* buffer, Handle debug, u32 addr, u32 size); + +/** + * @brief Writes to a debugged process's memory. + * @param debug Debug handle of the process. + * @param buffer Buffer to write data from. + * @param addr Address to write to. + * @param size Size of the memory to write. + */ +Result svcWriteProcessMemory(Handle debug, const void* buffer, u32 addr, u32 size); + +/** + * @brief Sets an hardware breakpoint or watchpoint. This is an interface to the BRP/WRP registers, see http://infocenter.arm.com/help/topic/com.arm.doc.ddi0360f/CEGEBGFC.html . + * @param registerId range 0..5 = breakpoints (BRP0-5), 0x100..0x101 = watchpoints (WRP0-1). The previous stop point for the register is disabled. + * @param control Value of the control regiser. + * @param value Value of the value register: either and address (if bit21 of control is clear) or the debug handle of a process to fetch the context ID of. + */ +Result svcSetHardwareBreakPoint(s32 registerId, u32 control, u32 value); + +/** + * @brief Gets a debugged thread's parameter. + * @param[out] unused Unused. + * @param[out] out Output value. + * @param debug Debug handle of the process. + * @param threadId ID of the thread + * @param parameter Parameter to fetch, see @ref DebugThreadParameter. + */ +Result svcGetDebugThreadParam(s64* unused, u32* out, Handle debug, u32 threadId, DebugThreadParameter parameter); + ///@} /** @@ -974,3 +1086,6 @@ Result svcContinueDebugEvent(Handle debug, u32 flags); * @param callback Function to execute. */ Result svcBackdoor(s32 (*callback)(void)); + +/// Stop point, does nothing if the process is not attached (as opposed to 'bkpt' instructions) +#define SVC_STOP_POINT __asm__ volatile("svc 0xFF"); diff --git a/libctru/include/3ds/types.h b/libctru/include/3ds/types.h index c694db5..4134cb8 100644 --- a/libctru/include/3ds/types.h +++ b/libctru/include/3ds/types.h @@ -58,3 +58,22 @@ typedef void (*voidfn)(void); /// Flags a function as deprecated. #define DEPRECATED #endif + +/// Structure representing CPU registers +typedef struct { + u32 r[13]; ///< r0-r12. + u32 sp; ///< sp. + u32 lr; ///< lr. + u32 pc; ///< pc. May need to be adjusted. + u32 cpsr; ///< cpsr. +} CpuRegisters; + +/// Structure representing FPU registers +typedef struct { + union{ + double d[16]; ///< d0-d15. + float f[32]; ///< f0-f31. + }; + u32 fpscr; ///< fpscr. + u32 fpexc; ///< fpexc. +} FpuRegisters; diff --git a/libctru/source/svc.s b/libctru/source/svc.s index 5447e5e..d1a3e10 100644 --- a/libctru/source/svc.s +++ b/libctru/source/svc.s @@ -430,6 +430,25 @@ SVC_BEGIN svcGetProcessList add sp, sp, #8 bx lr +SVC_BEGIN svcGetDebugThreadContext + svc 0x67 + bx lr + +SVC_BEGIN svcSetDebugThreadContext + svc 0x68 + bx lr + +SVC_BEGIN svcQueryDebugProcessMemory + push {r0, r1, r4-r6} + svc 0x69 + ldr r6, [sp] + stm r6, {r1-r4} + ldr r6, [sp, #4] + str r5, [r6] + add sp, sp, #8 + pop {r4-r6} + bx lr + SVC_BEGIN svcReadProcessMemory svc 0x6A bx lr @@ -438,6 +457,20 @@ SVC_BEGIN svcWriteProcessMemory svc 0x6B bx lr +SVC_BEGIN svcSetHardwareBreakPoint + svc 0x6C + bx lr + +SVC_BEGIN svcGetDebugThreadParam + push {r0, r1, r4, r5} + ldr r0, [sp, #16] + svc 0x6D + pop {r4, r5} + stm r4, {r1, r2} + str r3, [r5] + pop {r4, r5} + bx lr + SVC_BEGIN svcControlProcessMemory push {r4-r5} ldr r4, [sp, #0x8]