diff options
Diffstat (limited to 'gdb/arc-remote-fileio.c')
-rw-r--r-- | gdb/arc-remote-fileio.c | 741 |
1 files changed, 741 insertions, 0 deletions
diff --git a/gdb/arc-remote-fileio.c b/gdb/arc-remote-fileio.c new file mode 100644 index 0000000..5bb22ea --- /dev/null +++ b/gdb/arc-remote-fileio.c @@ -0,0 +1,741 @@ +/* Target dependent code for ARC processor family, for GDB, the GNU debugger. + + Copyright 2008, 2009 Free Software Foundation, Inc. + + Contributed by ARC International (www.arc.com) + + Author: + Richard Stuckey <richard.stuckey@arc.com> + + This file is part of GDB. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +/******************************************************************************/ +/* */ +/* Outline: */ +/* This module implements facilities for intercepting I/O (and other) */ +/* operations attempted on an ARC target and performing them on the host, */ +/* using a RPC (Remote Procedure Call) mechanism. */ +/* */ +/* Mechanism: */ +/* When interception is enabled, this module sets a breakpoint at the */ +/* entry point of each operation which is to be intercepted; it finds the */ +/* entry points of named routines in the C runtime library code by using */ +/* gdb's symbol lookup facilities. */ +/* */ +/* When a breakpoint is triggered on the target, the target monitoring */ +/* loop calls the function 'arc_check_interception_breakpoint' to check */ +/* whether the breakpoint triggered is an interception breakpoint; this */ +/* function will return a code indicating either */ +/* */ +/* a) the breakpoint is an interception breakpoint, the interception */ +/* has been performed and execution of the target program should be */ +/* resumed; or */ +/* */ +/* b) the breakpoint is an interception breakpoint, but the intercepted */ +/* routine is 'exit' and execution should not be resumed; or */ +/* */ +/* c) the breakpoint is not an interception breakpoint, so execution */ +/* should not be resumed and the trigger should be reported to gdb. */ +/* */ +/* In case a), this module then reads the routine's parameters from the */ +/* target's registers, performs whatever conversions are required, and */ +/* constructs a gdb RSP File I/O extension 'F' message which it passes to */ +/* the gdb target_fileio module, which performs the requested operation */ +/* on the host machine. */ +/* */ +/* The target_fileio module is passed a set of operations which allow it */ +/* to read data from target memory, write data to target memory, and */ +/* return a result value (and possibly a error code) to the intercepted */ +/* routine. The result value is written into the target's R0 register; */ +/* the error code (if any) is written into the location of the 'errno' */ +/* variable. */ +/* */ +/* Finally, this module copies the routine return address from the BLINK */ +/* register to the PC register - this ensures that when execution of the */ +/* target is resumed, control returns to the code after the call to the */ +/* intercepted routine. */ +/* */ +/* Notes: */ +/* 1) the set of routines to be intercepted, and the parameters to these */ +/* routines, is defined by a table (see below) - so it is simple to */ +/* add more routines to the set; */ +/* */ +/* 2) the 'open' syscall (see man open(2)) has a 'flags' parameter which */ +/* is a bit mask; unfortunately, the bits differ in meaning between */ +/* the host and the elf-32 target program, so the parameter must be */ +/* converted before it can be passed to the target_fileio module; */ +/* */ +/* 3) the 'fstat' syscall (see man fstat(2)) has an out parameter which */ +/* is a 'struct stat' structure (i.e. the address of such a structure */ +/* is a parameter to the syscall): the target_fileio module writes the */ +/* data of the structure to that location in target memory; however, */ +/* the structure does not have the same layout on host and target, so */ +/* the structure must be converted before it can be written to the */ +/* target; */ +/* */ +/* 4) the interception breakpoints are not handled by the core gdb */ +/* breakpoint mechanism; hence, they are not listed by the 'info break'*/ +/* command, and can not be (accidentally) deleted by the user; though */ +/* they could be handled by gdb, that would require the introduction */ +/* of a new "invisible" breakpoint type, and hence more changes to */ +/* supposedly generic code; */ +/* */ +/* 5) it would be more elegant (from one perspective) to intecept these */ +/* operations by placing a breakpoint at the interrupt vector location */ +/* of the 'swi' (SoftWare Interrupt) handler: only one breakpoint */ +/* would then be required, and all syscalls would be intercepted; */ +/* however, this module would then have to simulate a "return from */ +/* exception" in order to resume target execution, which would be more */ +/* complex than the "restart at return address" method currently used. */ +/* */ +/* Restrictions: */ +/* 1) this module places s/w breakpoints at the entry points; therefore, */ +/* the mechanism will not work with programs that are loaded into read */ +/* -only memory; */ +/* */ +/* 2) this mechanism will probably not work if the user sets breakpoints */ +/* on the entry points of the intercepted routines - there will be a */ +/* conflict! */ +/* */ +/******************************************************************************/ + +/* system header files */ +#include <stdio.h> +#include <string.h> +#include <signal.h> + +/* gdb header files */ +#include "defs.h" +#include "symtab.h" +#include "frame.h" +#include "block.h" +#include "target.h" +#include "target-fileio.h" +#include "exceptions.h" +#include "gdb/fileio.h" + +/* ARC header files */ +#include "arc-remote-fileio.h" +#include "config/arc/tm-embed.h" + + +/* -------------------------------------------------------------------------- */ +/* local types */ +/* -------------------------------------------------------------------------- */ + +#define MAX_SYSCALL_PARAMS 4 + +/* These are the intercepted routines. */ +typedef enum +{ + READ_CALL, + WRITE_CALL, + OPEN_CALL, + CLOSE_CALL, + LSEEK_CALL, + FSTAT_CALL, + GETTIMEOFDAY_CALL, + EXIT_CALL +} SystemCall; + + +struct lib_function +{ + SystemCall call; + const char *name; + const char *format; + Boolean bp_is_set; + unsigned int param_count; + ARC_RegisterNumber param_register[MAX_SYSCALL_PARAMS]; + struct bp_target_info breakpoint; +}; + + +/* This structure defines a memory image of the 'stat' structure as it is + represented on the ARC target. */ +struct arc_stat +{ + ARC_Byte fst_dev [4]; + ARC_Byte fst_ino [4]; + ARC_Byte fst_mode [2]; + ARC_Byte fst_nlink [2]; + ARC_Byte fst_uid [2]; + ARC_Byte fst_gid [2]; + ARC_Byte fst_rdev [4]; + ARC_Byte fst_size [4]; + ARC_Byte fst_blksize[4]; + ARC_Byte fst_blocks [4]; + ARC_Byte fst_atime [8]; + ARC_Byte fst_mtime [8]; + ARC_Byte fst_ctime [8]; +}; + + +/* -------------------------------------------------------------------------- */ +/* local data */ +/* -------------------------------------------------------------------------- */ + +/* The %x specifiers in the format strings in this table correspond to the + parameters to the intercepted functions; the register number of the target + register in which the parameter is passed is given by the corresponding + entry in the param_register array. + + N.B. the special value of SL as the number of the register in which a + parameter is passed indicates that the preceding parameter was the + address of a string, and the length of that string (including the + terminating NUL) is required here. + + F<n> indicates that the parameter in register <n> is a word of flag + bits which must be handled specially! + + X indicates that the array element is not used. */ + +#define SL 1000 +#define F2 1002 +#define X 9999 + +static struct lib_function functions[] = +{ + { READ_CALL, "_read_r", "Fread,%x,%x,%x", FALSE, 3, {1, 2, 3, X} }, + { WRITE_CALL, "_write_r", "Fwrite,%x,%x,%x", FALSE, 3, {1, 2, 3, X} }, + { OPEN_CALL, "_open_r", "Fopen,%x/%x,%x,%x", FALSE, 4, {1, SL, F2, 3} }, + { CLOSE_CALL, "_close_r", "Fclose,%x", FALSE, 1, {1, X, X, X} }, + { LSEEK_CALL, "_lseek_r", "Flseek,%x,%x,%x", FALSE, 3, {1, 2, 3, X} }, + { FSTAT_CALL, "_fstat_r", "Ffstat,%x,%x", FALSE, 2, {1, 2, X, X} }, + { GETTIMEOFDAY_CALL, "_gettimeofday_r", "Fgettimeofday,%x,%x", FALSE, 2, {1, 2, X, X} }, + { EXIT_CALL, "_exit_r", NULL, FALSE, 1, {1, X, X, X} } +}; + + +/* A pointer to the set of target operations for the current target. */ +static TargetOperations *target_operations; + +/* TRUE if the operation currently being intercepted has NOT been interrupted + by the user typing a Ctrl-C. */ +static Boolean not_interrupted; + +/* For the Ctrl-C signal handler. */ +static void (*old_signal_handler) (int); + + +/* -------------------------------------------------------------------------- */ +/* local functions */ +/* -------------------------------------------------------------------------- */ + +/* Read a number of bytes of data from the given address in target memory. */ + +static int +read_bytes (CORE_ADDR memaddr, gdb_byte *myaddr, int len) +{ + DEBUG("reading %d bytes from %x\n", len, (unsigned int) memaddr); + + return (int) target_read (¤t_target, TARGET_OBJECT_MEMORY, NULL, myaddr, memaddr, len); +} + + +/* Read a number of bytes of data from the given address in target memory. */ + +static int +write_bytes (CORE_ADDR memaddr, gdb_byte *myaddr, int len) +{ + DEBUG("writing %d bytes to %x\n", len, (unsigned int) memaddr); + + return (int) target_write (¤t_target, TARGET_OBJECT_MEMORY, NULL, myaddr, memaddr, len); +} + + +/* Perform the reply to the intercepted operation: set up the result of the call + and the error code (if any) so that the intercepted operation receives them + just as though the operation had really been performed upon the target. */ + +static void +reply (int retcode, int error) +{ + /* Ignore any Ctrl-Cs while performing the reply. */ + (void) signal (SIGINT, SIG_IGN); + + DEBUG("reply: retcode = %d, error = %d\n", retcode, error); + + /* If an error has occurred. */ + if (retcode == -1) + { + ARC_RegisterContents errno_address; + + if (error == FILEIO_EINTR) + { + DEBUG("*** interrupted by user!\n"); + /* Set the global flag so that it can be tested later. */ + not_interrupted = FALSE; + return; + } + + /* Read the address of the 'errno' variable from R0. */ + if (target_operations->read_core_register(0, &errno_address, TRUE)) + /* Write the error number into the 'errno' variable. */ + (void) write_bytes((CORE_ADDR) errno_address, (gdb_byte*) &error, BYTES_IN_WORD); + } + + /* Write the return code into the function result register R0. */ + (void) target_operations->write_core_register(0, (ARC_RegisterContents) retcode, TRUE); +} + + +/* Copy a number of bytes of data from one buffer to another. Note that the + buffers are not necessarily of the same size. Perform endianess byte-swapping + if necessary. */ + +static void +copy_bytes (char *from, size_t from_size, + ARC_Byte *to, size_t to_size, + Boolean target_is_big_endian) +{ + /* We can not copy more data than we have been given in the source buffer, + or for which there is room in the destination buffer. */ + unsigned int bytes = (unsigned int) ((from_size > to_size) ? to_size : from_size); + unsigned int i; + + /* N.B. 1) the fio_stat structure created by target-fileio.c has the values + in big-endian byte order; so if the ARC target is little-endian + we must reverse the order; + + 2) the fields in the fio_stat structure may be smaller (or larger!) + than the corresponding fields in the ARC target structure - so we + copy the *least* significant bytes of the fields, on the grounds + that the most significant bytes are probably just sign-extensions! */ + for (i = 0; i < bytes; i++) + to[i] = (ARC_Byte) ((target_is_big_endian) ? from[i] + : from[from_size - 1 - i]); +} + + +/* Write a 'stat' structure to the target at a given address in memory. + 'myaddr' points to a fio_stat structure created by the target-fileio module; + this structure is meant for use in the RSP protocol, and is designed for + independence of host/target systems - therefore, we must create an equivalent + structure which is ARC-specific, and write that structure to the target. + + Return the number of bytes of data written. */ + +static int +write_fstat (CORE_ADDR memaddr, gdb_byte *myaddr, int len) +{ + Boolean target_is_big_endian = (gdbarch_byte_order (current_gdbarch) == BFD_ENDIAN_BIG); + struct fio_stat *fst = (struct fio_stat*) myaddr; + struct arc_stat ast; + + memset(&ast, 0, sizeof(ast)); + +#define COPY(from, to) copy_bytes(from, sizeof(from), to, sizeof(to), target_is_big_endian) + + COPY(fst->fst_dev, ast.fst_dev); + COPY(fst->fst_ino, ast.fst_ino); + COPY(fst->fst_mode, ast.fst_mode); + COPY(fst->fst_nlink, ast.fst_nlink); + COPY(fst->fst_uid, ast.fst_uid); + COPY(fst->fst_gid, ast.fst_gid); + COPY(fst->fst_rdev, ast.fst_rdev); + COPY(fst->fst_size, ast.fst_size); + COPY(fst->fst_blksize, ast.fst_blksize); + COPY(fst->fst_blocks, ast.fst_blocks); + COPY(fst->fst_atime, ast.fst_atime); + COPY(fst->fst_mtime, ast.fst_mtime); + COPY(fst->fst_ctime, ast.fst_ctime); + + return write_bytes(memaddr, (gdb_byte*) &ast, (int) sizeof(ast)); +} + + +/* Find the address of the entry point of the given routine in the target program. + Return 0 if the entry point can not be found, or is not a function. */ + +static CORE_ADDR +findEntryPoint (const char *routine) +{ + CORE_ADDR entry = 0; + int is_a_field_of_this; + struct symbol *sym = lookup_symbol (routine, + get_selected_block (0), + VAR_DOMAIN, + &is_a_field_of_this, + (struct symtab **) NULL); + if (sym) + { + if (SYMBOL_CLASS (sym) == LOC_BLOCK) + entry = BLOCK_START (SYMBOL_BLOCK_VALUE (sym)); + else + warning(_("%s is not a function"), routine); + } + else + warning(_("can not find entry point of function %s"), routine); + + return entry; +} + + +/* Insert a s/w breakpoint in the target program code at the entry point of the + given library function. */ + +static void +insert_breakpoint (struct lib_function *f) +{ + if (!f->bp_is_set) + { + if (arc_elf32_insert_breakpoint(&f->breakpoint) == 0) + f->bp_is_set = TRUE; + else + warning(_("can not set breakpoint at entrypoint of %s"), f->name); + } +} + + +/* Remove a s/w breakpoint from the target program code at the entry point of the + given library function. */ + +static void +remove_breakpoint (struct lib_function *f) +{ + if (f->bp_is_set) + { + if (arc_elf32_remove_breakpoint(&f->breakpoint) == 0) + f->bp_is_set = FALSE; + else + warning(_("can not unset breakpoint at entrypoint of %s"), f->name); + } +} + + +/* This function handles any Ctrl-C typed by the user whilst the interception of + an operation is in progress. */ + +static void +Ctrl_C_signal_handler (int signo) +{ + /* Ignore any more Ctrl-Cs. */ + (void) signal (SIGINT, SIG_IGN); + + /* We must use the gdb exception mechanism since the target_fileio_request + function calls catch_exceptions, and if we do something else (like a long + jump) here, gdb's cleanup list would be left in an inconsistent state! */ + DEBUG("*** throwing RETURN_QUIT...\n"); + deprecated_throw_reason (RETURN_QUIT); +} + + +/* This function is called from the gdb target-fileio module: it sets up this + module's handler for Ctrl-C interrupts. */ + +static void +set_Ctrl_C_signal_handler (void) +{ + old_signal_handler = signal (SIGINT, Ctrl_C_signal_handler); +} + + +/* This function finds the length of a C string stored in target memory at the + given address. */ + +static unsigned int +find_string_length (ARC_Address address) +{ + unsigned int length = 0; + + while (TRUE) + { + gdb_byte buf[65]; + int bytes = read_bytes((CORE_ADDR) address, + buf, + (int) sizeof(buf) - 1); + int i; + + for (i = 0; i < bytes; i++) + if (buf[i] == (gdb_byte) '\0') + return (unsigned int) (length + i + 1); + + address += bytes; + length += bytes; + } +} + + +/* Convert flags to target syscall to what they "should" be! */ + +static ARC_RegisterContents +convert_flags (ARC_RegisterContents flags) +{ + ARC_RegisterContents result = flags; + +/* See gcc/src/newlib/libc/sys/arc/sys/fcntl.h */ + +/* The following values have been changed for uclibc compatibility. */ +#define _FAPPEND 0x0400 /* append (writes guaranteed at the end) */ +#define _FASYNC 0x2000 /* signal pgrp when data ready */ +#define _FCREAT 0x0040 /* open with file create */ +#define _FTRUNC 0x0200 /* open with truncation */ +#define _FEXCL 0x0080 /* error on open if file exists */ +#define _FSYNC 0x1000 /* do all writes synchronously */ +#define _FNONBLOCK 0x0800 /* non blocking I/O (POSIX style) */ + +#define REMOVE(flag) if (flags & _F ## flag) result &= ~ _F ## flag +#define ADD(flag) if (flags & _F ## flag) result |= FILEIO_O_ ## flag + + /* N.B. all "old" bits most be removed from the result word before all + "new" bits are added, in case the old and new sets intersect! */ + REMOVE(APPEND); +// REMOVE(ASYNC); // no equivalent flag in gdb/fileio.h + REMOVE(CREAT); + REMOVE(TRUNC); + REMOVE(EXCL); +// REMOVE(SYNC); // no equivalent flag in gdb/fileio.h +// REMOVE(NONBLOCK); // no equivalent flag in gdb/fileio.h + ADD(APPEND); +// ADD(ASYNC); // no equivalent flag in gdb/fileio.h + ADD(CREAT); + ADD(TRUNC); + ADD(EXCL); +// ADD(SYNC); // no equivalent flag in gdb/fileio.h +// ADD(NONBLOCK); // no equivalent flag in gdb/fileio.h + + return result; +} + + +/* Perform the interception of the given library function. + Return TRUE if the interception is completed successfully, + FALSE if it is interrupted by the user. */ + +static Boolean +perform_interception (struct lib_function *f) +{ + ARC_RegisterContents params [MAX_SYSCALL_PARAMS]; + char request[MAX_SYSCALL_PARAMS * 9 + 40]; + unsigned int i; + + /* These operations allow the target_fileio module to read data from target + memory, write data to target memory, and return a result value (and + possibly a error code) to the intercepted routine. + + N.B. if the syscsall is 'fstat', we pass a special write function + which converts the 'struct stat' structure to target layout before + writing it to target memory. */ + struct file_io_operations io_operations = + { + read_bytes, + (f->call == FSTAT_CALL) ? write_fstat : write_bytes, + reply, + set_Ctrl_C_signal_handler + }; + + /* Evaluate the parameters to be passed to the RPC request. */ + for (i = 0; i < f->param_count; i++) + { + ARC_RegisterNumber reg = f->param_register[i]; + + if (reg == SL) + params[i] = find_string_length((ARC_Address) params[i - 1]); + else if (reg == F2) + { + ARC_RegisterContents flags; + + (void) target_operations->read_core_register(2, &flags, TRUE); + params[i] = convert_flags(flags); + } + else + (void) target_operations->read_core_register(reg, ¶ms[i], TRUE); + } + + /* Do not close the target program's stdin, stdout or stderr streams on the + host: it is possible that the program may be re-loaded and re-run on the + target in the same debugging session (so re-initializing its I/O system) + so it may try to read/write those streams again - instead, just tell the + target that the close succeeded. */ + if (f->call == CLOSE_CALL) + { + int fd = (int) params[0]; + + if (fd == STDIN_FILENO || fd == STDOUT_FILENO || fd == STDERR_FILENO) + { + reply(0, 0); + DEBUG("*** RPC close of stream %d ignored\n", fd); + return TRUE; + } + } + + /* Parameters which are extra to those required by the format will simply be + ignored. */ + (void) snprintf(request, sizeof(request), f->format, + params[0], params[1], params[2], params[3]); + + DEBUG("RPC request: %s\n", request); + + /* the interception might be interrupted by the user typing Ctrl-C whilst + the interception is in progress; if that happens, this flag will be set + to FALSE. */ + not_interrupted = TRUE; + + /* Make the RPC request. */ + target_fileio_request(request, &io_operations); + + (void) signal (SIGINT, old_signal_handler); + + /* If the call was not interrupted, the interception has been performed. */ + return not_interrupted; +} + + +/* -------------------------------------------------------------------------- */ +/* externally visible functions */ +/* -------------------------------------------------------------------------- */ + +/* Set the state of the I/O interception mechanism: + ON : set breakpoints on all the functions to be intercepted + OFF : clear breakpoints from all the intercepted functions + RESET: mark the breakpoints as not being set (if a new program has been + downloaded to the target, the s/w breakpoints in the old program + have been lost, and so should not be removed). */ + +void +arc_set_IO_interception (TargetOperations *operations, + InterceptionState state) +{ + unsigned int i; + + DEBUG("*** interception: %s\n", (state == INTERCEPTION_RESET) ? "RESET" : + (state == INTERCEPTION_ON) ? "ON" : + "OFF"); + + target_operations = operations; + + for (i = 0; i < ELEMENTS_IN_ARRAY(functions); i++) + { + struct lib_function* f = &functions[i]; + + switch (state) + { + case INTERCEPTION_RESET: + f->bp_is_set = FALSE; + break; + + case INTERCEPTION_ON: + /* Set a breakpoint on the entry point of the function. */ + f->breakpoint.placed_address = findEntryPoint(f->name); + + if (f->breakpoint.placed_address != 0) + { + DEBUG("intercept 0x%08X : %s\n", (unsigned int) f->breakpoint.placed_address, f->name); + insert_breakpoint(f); + } + break; + + case INTERCEPTION_OFF: + if (f->breakpoint.placed_address != 0) + { + remove_breakpoint(f); + f->breakpoint.placed_address = 0; + } + break; + } + } +} + + +/* This function is called when the execution of the target program has been + halted by a breakpoint trigger. It checks whether the breakpoint that has + been triggered is at the entry point of an intercepted function, and, if so, + performs the required interception. + + Returns: + INTERCEPTION_RESUME : interception has been performed, execution should be resumed + INTERCEPTION_HALT : the program is halted (no interception has been performed) + INTERCEPTION_EXIT : the program has exited + INTERCEPTION_INTERRUPT : the interception has been interrupted by the user + + If the program has exited, the 'exitcode' parameter is set to the program's exit code. */ + +InterceptionAction +arc_check_interception_breakpoint (int *exitcode) +{ + ARC_RegisterContents pc; + + ENTERMSG; + + *exitcode = 0; + + /* Get the current execution point from the PCL, rather than the PC - this + gives the same result on both ARC700 and ARC600 targets. */ + if (target_operations->read_core_register(ARC_PCL_REGNUM, &pc, TRUE)) + { + unsigned int i; + + DEBUG("checking for interception at 0x%08X\n", pc); + + /* Look at each of the intercepted operations. */ + for (i = 0; i < ELEMENTS_IN_ARRAY(functions); i++) + { + struct lib_function *f = &functions[i]; + + if (f->breakpoint.placed_address == (CORE_ADDR) pc) + { + DEBUG("intercepted function %s\n", f->name); + + if (f->call == EXIT_CALL) + { + ARC_RegisterContents code; + + /* The exit code is in parameter register R1. */ + if (target_operations->read_core_register(1, &code, TRUE)) + *exitcode = (int) code; + + return INTERCEPTION_EXIT; + } + else + { + /* If the interception is performed. */ + if (perform_interception(f)) + { + ARC_RegisterContents blink; + + /* Copy BLINK to PC, so that when execution is re-started, + control will return to the point after the call of the + intercepted function. */ + if (target_operations->read_core_register (ARC_BLINK_REGNUM, &blink, TRUE) && + target_operations->write_auxiliary_register(arc_pc_regnum, blink, TRUE)) + { + DEBUG("copied BLINK (%x) to PC (was %x)\n", blink, pc); + return INTERCEPTION_RESUME; + } + + /* If we couldn't set PC, fall through. */ + } + else + { + /* The interception has been interrupted by a Ctrl-C + from the user - do not change the PC, as we want + execution to resume at the same point in the code, + so that the I/O request will be performed (and + intercepted) again: this e.g. allows the user to + break into a program that is in a tight loop doing + reads or writes. */ + return INTERCEPTION_INTERRUPT; + } + } + } + } + } + + return INTERCEPTION_HALT; +} + +/******************************************************************************/ |