blob: 5bb22eafe31e0de43db3540a60474f91aa804c44 [file] [log] [blame]
/* 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 (&current_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 (&current_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, &params[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;
}
/******************************************************************************/