/* Simulator memory option handling.
Copyright (C) 1996-2022 Free Software Foundation, Inc.
Contributed by Cygnus Support.
This file is part of GDB, the GNU debugger.
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 . */
/* This must come before any other includes. */
#include "defs.h"
#include
#ifdef HAVE_FCNTL_H
#include
#endif
#include
#include
#ifdef HAVE_UNISTD_H
#include
#endif
#ifdef HAVE_SYS_MMAN_H
#include
#endif
#ifdef HAVE_SYS_STAT_H
#include
#endif
#include "sim-main.h"
#include "sim-assert.h"
#include "sim-options.h"
/* Memory fill byte. */
static uint8_t fill_byte_value;
static int fill_byte_flag = 0;
/* Memory mapping; see OPTION_MEMORY_MAPFILE. */
static int mmap_next_fd = -1;
/* Memory command line options. */
enum {
OPTION_MEMORY_DELETE = OPTION_START,
OPTION_MEMORY_REGION,
OPTION_MEMORY_SIZE,
OPTION_MEMORY_INFO,
OPTION_MEMORY_ALIAS,
OPTION_MEMORY_CLEAR,
OPTION_MEMORY_FILL,
OPTION_MEMORY_MAPFILE,
OPTION_MAP_INFO
};
static DECLARE_OPTION_HANDLER (memory_option_handler);
static const OPTION memory_options[] =
{
{ {"memory-delete", required_argument, NULL, OPTION_MEMORY_DELETE },
'\0', "ADDRESS|all", "Delete memory at ADDRESS (all addresses)",
memory_option_handler },
{ {"delete-memory", required_argument, NULL, OPTION_MEMORY_DELETE },
'\0', "ADDRESS", NULL,
memory_option_handler },
{ {"memory-region", required_argument, NULL, OPTION_MEMORY_REGION },
'\0', "ADDRESS,SIZE[,MODULO]", "Add a memory region",
memory_option_handler },
{ {"memory-alias", required_argument, NULL, OPTION_MEMORY_ALIAS },
'\0', "ADDRESS,SIZE{,ADDRESS}", "Add memory shadow",
memory_option_handler },
{ {"memory-size", required_argument, NULL, OPTION_MEMORY_SIZE },
'\0', "[in bytes, Kb (k suffix), Mb (m suffix) or Gb (g suffix)]",
"Add memory at address zero", memory_option_handler },
{ {"memory-fill", required_argument, NULL, OPTION_MEMORY_FILL },
'\0', "VALUE", "Fill subsequently added memory regions",
memory_option_handler },
{ {"memory-clear", no_argument, NULL, OPTION_MEMORY_CLEAR },
'\0', NULL, "Clear subsequently added memory regions",
memory_option_handler },
#if defined(HAVE_MMAP) && defined(HAVE_MUNMAP)
{ {"memory-mapfile", required_argument, NULL, OPTION_MEMORY_MAPFILE },
'\0', "FILE", "Memory-map next memory region from file",
memory_option_handler },
#endif
{ {"memory-info", no_argument, NULL, OPTION_MEMORY_INFO },
'\0', NULL, "List configurable memory regions",
memory_option_handler },
{ {"info-memory", no_argument, NULL, OPTION_MEMORY_INFO },
'\0', NULL, NULL,
memory_option_handler },
{ {"map-info", no_argument, NULL, OPTION_MAP_INFO },
'\0', NULL, "List mapped regions",
memory_option_handler },
{ {NULL, no_argument, NULL, 0}, '\0', NULL, NULL, NULL }
};
static sim_memopt *
do_memopt_add (SIM_DESC sd,
int level,
int space,
address_word addr,
address_word nr_bytes,
unsigned modulo,
sim_memopt **entry,
void *buffer)
{
void *fill_buffer;
unsigned fill_length;
void *free_buffer;
unsigned long free_length;
if (buffer != NULL)
{
/* Buffer already given. sim_memory_uninstall will free it. */
sim_core_attach (sd, NULL,
level, access_read_write_exec, space,
addr, nr_bytes, modulo, NULL, buffer);
free_buffer = buffer;
free_length = 0;
fill_buffer = buffer;
fill_length = (modulo == 0) ? nr_bytes : modulo;
}
else
{
/* Allocate new well-aligned buffer, just as sim_core_attach(). */
void *aligned_buffer;
int padding = (addr % sizeof (uint64_t));
unsigned long bytes;
#ifdef HAVE_MMAP
struct stat s;
if (mmap_next_fd >= 0)
{
/* Check that given file is big enough. */
int rc = fstat (mmap_next_fd, &s);
if (rc < 0)
sim_io_error (sd, "Error, unable to stat file: %s\n",
strerror (errno));
/* Autosize the mapping to the file length. */
if (nr_bytes == 0)
nr_bytes = s.st_size;
}
#endif
bytes = (modulo == 0 ? nr_bytes : modulo) + padding;
free_buffer = NULL;
free_length = bytes;
#ifdef HAVE_MMAP
/* Memory map or malloc(). */
if (mmap_next_fd >= 0)
{
/* Some kernels will SIGBUS the application if mmap'd file
is not large enough. */
if (s.st_size < bytes)
{
sim_io_error (sd,
"Error, cannot confirm that mmap file is large enough "
"(>= %ld bytes)\n", bytes);
}
free_buffer = mmap (0, bytes, PROT_READ|PROT_WRITE, MAP_SHARED, mmap_next_fd, 0);
if (free_buffer == 0 || free_buffer == (char*)-1) /* MAP_FAILED */
{
sim_io_error (sd, "Error, cannot mmap file (%s).\n",
strerror (errno));
}
}
#endif
/* Need heap allocation? */
if (free_buffer == NULL)
{
/* If filling with non-zero value, do not use clearing allocator. */
if (fill_byte_flag && fill_byte_value != 0)
free_buffer = xmalloc (bytes); /* don't clear */
else
free_buffer = zalloc (bytes); /* clear */
}
aligned_buffer = (char*) free_buffer + padding;
sim_core_attach (sd, NULL,
level, access_read_write_exec, space,
addr, nr_bytes, modulo, NULL, aligned_buffer);
fill_buffer = aligned_buffer;
fill_length = (modulo == 0) ? nr_bytes : modulo;
/* If we just used a clearing allocator, and are about to fill with
zero, truncate the redundant fill operation. */
if (fill_byte_flag && fill_byte_value == 0)
fill_length = 1; /* avoid boundary length=0 case */
}
if (fill_byte_flag)
{
ASSERT (fill_buffer != 0);
memset ((char*) fill_buffer, fill_byte_value, fill_length);
}
while ((*entry) != NULL)
entry = &(*entry)->next;
(*entry) = ZALLOC (sim_memopt);
(*entry)->level = level;
(*entry)->space = space;
(*entry)->addr = addr;
(*entry)->nr_bytes = nr_bytes;
(*entry)->modulo = modulo;
(*entry)->buffer = free_buffer;
/* Record memory unmapping info. */
if (mmap_next_fd >= 0)
{
(*entry)->munmap_length = free_length;
close (mmap_next_fd);
mmap_next_fd = -1;
}
else
(*entry)->munmap_length = 0;
return (*entry);
}
static SIM_RC
do_memopt_delete (SIM_DESC sd,
int level,
int space,
address_word addr)
{
sim_memopt **entry = &STATE_MEMOPT (sd);
sim_memopt *alias;
while ((*entry) != NULL
&& ((*entry)->level != level
|| (*entry)->space != space
|| (*entry)->addr != addr))
entry = &(*entry)->next;
if ((*entry) == NULL)
{
sim_io_eprintf (sd, "Memory at 0x%lx not found, not deleted\n",
(long) addr);
return SIM_RC_FAIL;
}
/* delete any buffer */
if ((*entry)->buffer != NULL)
{
#ifdef HAVE_MUNMAP
if ((*entry)->munmap_length > 0)
munmap ((*entry)->buffer, (*entry)->munmap_length);
else
#endif
free ((*entry)->buffer);
}
/* delete it and its aliases */
alias = *entry;
*entry = (*entry)->next;
while (alias != NULL)
{
sim_memopt *dead = alias;
alias = alias->alias;
sim_core_detach (sd, NULL, dead->level, dead->space, dead->addr);
free (dead);
}
return SIM_RC_OK;
}
static char *
parse_size (char *chp,
address_word *nr_bytes,
unsigned *modulo)
{
/* [K|M|G] [ "%" ] */
*nr_bytes = strtoul (chp, &chp, 0);
switch (*chp)
{
case '%':
*modulo = strtoul (chp + 1, &chp, 0);
break;
case 'g': case 'G': /* Gigabyte suffix. */
*nr_bytes <<= 10;
/* Fall through. */
case 'm': case 'M': /* Megabyte suffix. */
*nr_bytes <<= 10;
/* Fall through. */
case 'k': case 'K': /* Kilobyte suffix. */
*nr_bytes <<= 10;
/* Check for a modulo specifier after the suffix. */
++ chp;
if (* chp == 'b' || * chp == 'B')
++ chp;
if (* chp == '%')
*modulo = strtoul (chp + 1, &chp, 0);
break;
}
return chp;
}
static char *
parse_ulong_value (char *chp,
unsigned long *value)
{
*value = strtoul (chp, &chp, 0);
return chp;
}
static char *
parse_addr (char *chp,
int *level,
int *space,
address_word *addr)
{
/* [ ": " ] [ "@" ] */
*addr = (unsigned long) strtoul (chp, &chp, 0);
if (*chp == ':')
{
*space = *addr;
*addr = (unsigned long) strtoul (chp + 1, &chp, 0);
}
if (*chp == '@')
{
*level = strtoul (chp + 1, &chp, 0);
}
return chp;
}
static SIM_RC
memory_option_handler (SIM_DESC sd, sim_cpu *cpu, int opt,
char *arg, int is_command)
{
switch (opt)
{
case OPTION_MEMORY_DELETE:
if (strcasecmp (arg, "all") == 0)
{
while (STATE_MEMOPT (sd) != NULL)
do_memopt_delete (sd,
STATE_MEMOPT (sd)->level,
STATE_MEMOPT (sd)->space,
STATE_MEMOPT (sd)->addr);
return SIM_RC_OK;
}
else
{
int level = 0;
int space = 0;
address_word addr = 0;
parse_addr (arg, &level, &space, &addr);
return do_memopt_delete (sd, level, space, addr);
}
case OPTION_MEMORY_REGION:
{
char *chp = arg;
int level = 0;
int space = 0;
address_word addr = 0;
address_word nr_bytes = 0;
unsigned modulo = 0;
/* parse the arguments */
chp = parse_addr (chp, &level, &space, &addr);
if (*chp != ',')
{
/* let the file autosize */
if (mmap_next_fd == -1)
{
sim_io_eprintf (sd, "Missing size for memory-region\n");
return SIM_RC_FAIL;
}
}
else
chp = parse_size (chp + 1, &nr_bytes, &modulo);
/* old style */
if (*chp == ',')
modulo = strtoul (chp + 1, &chp, 0);
/* try to attach/insert it */
do_memopt_add (sd, level, space, addr, nr_bytes, modulo,
&STATE_MEMOPT (sd), NULL);
return SIM_RC_OK;
}
case OPTION_MEMORY_ALIAS:
{
char *chp = arg;
int level = 0;
int space = 0;
address_word addr = 0;
address_word nr_bytes = 0;
unsigned modulo = 0;
sim_memopt *entry;
/* parse the arguments */
chp = parse_addr (chp, &level, &space, &addr);
if (*chp != ',')
{
sim_io_eprintf (sd, "Missing size for memory-region\n");
return SIM_RC_FAIL;
}
chp = parse_size (chp + 1, &nr_bytes, &modulo);
/* try to attach/insert the main record */
entry = do_memopt_add (sd, level, space, addr, nr_bytes, modulo,
&STATE_MEMOPT (sd),
NULL);
/* now attach all the aliases */
while (*chp == ',')
{
int a_level = level;
int a_space = space;
address_word a_addr = addr;
chp = parse_addr (chp + 1, &a_level, &a_space, &a_addr);
do_memopt_add (sd, a_level, a_space, a_addr, nr_bytes, modulo,
&entry->alias, entry->buffer);
}
return SIM_RC_OK;
}
case OPTION_MEMORY_SIZE:
{
int level = 0;
int space = 0;
address_word addr = 0;
address_word nr_bytes = 0;
unsigned modulo = 0;
/* parse the arguments */
parse_size (arg, &nr_bytes, &modulo);
/* try to attach/insert it */
do_memopt_add (sd, level, space, addr, nr_bytes, modulo,
&STATE_MEMOPT (sd), NULL);
return SIM_RC_OK;
}
case OPTION_MEMORY_CLEAR:
{
fill_byte_value = (uint8_t) 0;
fill_byte_flag = 1;
return SIM_RC_OK;
break;
}
case OPTION_MEMORY_FILL:
{
unsigned long fill_value;
parse_ulong_value (arg, &fill_value);
if (fill_value > 255)
{
sim_io_eprintf (sd, "Missing fill value between 0 and 255\n");
return SIM_RC_FAIL;
}
fill_byte_value = (uint8_t) fill_value;
fill_byte_flag = 1;
return SIM_RC_OK;
break;
}
case OPTION_MEMORY_MAPFILE:
{
if (mmap_next_fd >= 0)
{
sim_io_eprintf (sd, "Duplicate memory-mapfile option\n");
return SIM_RC_FAIL;
}
mmap_next_fd = open (arg, O_RDWR);
if (mmap_next_fd < 0)
{
sim_io_eprintf (sd, "Cannot open file `%s': %s\n",
arg, strerror (errno));
return SIM_RC_FAIL;
}
return SIM_RC_OK;
}
case OPTION_MEMORY_INFO:
{
sim_memopt *entry;
sim_io_printf (sd, "Memory maps:\n");
for (entry = STATE_MEMOPT (sd); entry != NULL; entry = entry->next)
{
sim_memopt *alias;
sim_io_printf (sd, " memory");
if (entry->alias == NULL)
sim_io_printf (sd, " region ");
else
sim_io_printf (sd, " alias ");
if (entry->space != 0)
sim_io_printf (sd, "0x%lx:", (long) entry->space);
sim_io_printf (sd, "0x%08lx", (long) entry->addr);
if (entry->level != 0)
sim_io_printf (sd, "@0x%lx", (long) entry->level);
sim_io_printf (sd, ",0x%lx",
(long) entry->nr_bytes);
if (entry->modulo != 0)
sim_io_printf (sd, "%%0x%lx", (long) entry->modulo);
for (alias = entry->alias;
alias != NULL;
alias = alias->next)
{
if (alias->space != 0)
sim_io_printf (sd, "0x%lx:", (long) alias->space);
sim_io_printf (sd, ",0x%08lx", (long) alias->addr);
if (alias->level != 0)
sim_io_printf (sd, "@0x%lx", (long) alias->level);
}
sim_io_printf (sd, "\n");
}
return SIM_RC_OK;
break;
}
case OPTION_MAP_INFO:
{
sim_core *memory = STATE_CORE (sd);
unsigned nr_map;
for (nr_map = 0; nr_map < nr_maps; ++nr_map)
{
sim_core_map *map = &memory->common.map[nr_map];
sim_core_mapping *mapping = map->first;
if (!mapping)
continue;
sim_io_printf (sd, "%s maps:\n", map_to_str (nr_map));
do
{
unsigned modulo;
sim_io_printf (sd, " map ");
if (mapping->space != 0)
sim_io_printf (sd, "0x%x:", mapping->space);
sim_io_printf (sd, "0x%08lx", (long) mapping->base);
if (mapping->level != 0)
sim_io_printf (sd, "@0x%x", mapping->level);
sim_io_printf (sd, ",0x%lx", (long) mapping->nr_bytes);
modulo = mapping->mask + 1;
if (modulo != 0)
sim_io_printf (sd, "%%0x%x", modulo);
sim_io_printf (sd, "\n");
mapping = mapping->next;
}
while (mapping);
}
return SIM_RC_OK;
break;
}
default:
sim_io_eprintf (sd, "Unknown memory option %d\n", opt);
return SIM_RC_FAIL;
}
return SIM_RC_FAIL;
}
/* "memory" module install handler.
This is called via sim_module_install to install the "memory" subsystem
into the simulator. */
static MODULE_INIT_FN sim_memory_init;
static MODULE_UNINSTALL_FN sim_memory_uninstall;
SIM_RC
sim_memopt_install (SIM_DESC sd)
{
SIM_ASSERT (STATE_MAGIC (sd) == SIM_MAGIC_NUMBER);
sim_add_option_table (sd, NULL, memory_options);
sim_module_add_uninstall_fn (sd, sim_memory_uninstall);
sim_module_add_init_fn (sd, sim_memory_init);
return SIM_RC_OK;
}
/* Uninstall the "memory" subsystem from the simulator. */
static void
sim_memory_uninstall (SIM_DESC sd)
{
sim_memopt **entry = &STATE_MEMOPT (sd);
sim_memopt *alias;
while ((*entry) != NULL)
{
/* delete any buffer */
if ((*entry)->buffer != NULL)
{
#ifdef HAVE_MUNMAP
if ((*entry)->munmap_length > 0)
munmap ((*entry)->buffer, (*entry)->munmap_length);
else
#endif
free ((*entry)->buffer);
}
/* delete it and its aliases */
alias = *entry;
/* next victim */
*entry = (*entry)->next;
while (alias != NULL)
{
sim_memopt *dead = alias;
alias = alias->alias;
sim_core_detach (sd, NULL, dead->level, dead->space, dead->addr);
free (dead);
}
}
}
void sim_dump_memory (SIM_DESC sd);
/* Convenience function for use when debugging the simulator, to be
called from within e.g. gdb. */
void
sim_dump_memory (SIM_DESC sd)
{
memory_option_handler (sd, NULL, OPTION_MEMORY_INFO, NULL, 0);
memory_option_handler (sd, NULL, OPTION_MAP_INFO, NULL, 0);
}
static SIM_RC
sim_memory_init (SIM_DESC sd)
{
/* Reinitialize option modifier flags, in case they were left
over from a previous sim startup event. */
fill_byte_flag = 0;
mmap_next_fd = -1;
return SIM_RC_OK;
}