| /* Simulator memory option handling. |
| Copyright (C) 1996-2021 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 <http://www.gnu.org/licenses/>. */ |
| |
| /* This must come before any other includes. */ |
| #include "defs.h" |
| |
| #include "sim-main.h" |
| #include "sim-assert.h" |
| #include "sim-options.h" |
| |
| #include <string.h> |
| #include <stdlib.h> |
| #include <errno.h> |
| #ifdef HAVE_FCNTL_H |
| #include <fcntl.h> |
| #endif |
| #ifdef HAVE_SYS_MMAN_H |
| #include <sys/mman.h> |
| #endif |
| #ifdef HAVE_SYS_STAT_H |
| #include <sys/stat.h> |
| #endif |
| #ifdef HAVE_UNISTD_H |
| #include <unistd.h> |
| #endif |
| |
| /* Memory fill byte. */ |
| static unsigned8 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', "<size>[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 (unsigned64)); |
| 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) |
| { |
| /* <nr_bytes>[K|M|G] [ "%" <modulo> ] */ |
| *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) |
| { |
| /* [ <space> ": " ] <addr> [ "@" <level> ] */ |
| *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 = (unsigned8) 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 = (unsigned8) 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); |
| } |
| } |
| } |
| |
| |
| 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; |
| } |