- /* Caching code for GDB, the GNU debugger.
- Copyright (C) 1992-2015 Free Software Foundation, Inc.
- 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/>. */
- #include "defs.h"
- #include "dcache.h"
- #include "gdbcmd.h"
- #include "gdbcore.h"
- #include "target-dcache.h"
- #include "inferior.h"
- #include "splay-tree.h"
- /* Commands with a prefix of `{set,show} dcache'. */
- static struct cmd_list_element *dcache_set_list = NULL;
- static struct cmd_list_element *dcache_show_list = NULL;
- /* The data cache could lead to incorrect results because it doesn't
- know about volatile variables, thus making it impossible to debug
- functions which use memory mapped I/O devices. Set the nocache
- memory region attribute in those cases.
- In general the dcache speeds up performance. Some speed improvement
- comes from the actual caching mechanism, but the major gain is in
- the reduction of the remote protocol overhead; instead of reading
- or writing a large area of memory in 4 byte requests, the cache
- bundles up the requests into LINE_SIZE chunks, reducing overhead
- significantly. This is most useful when accessing a large amount
- of data, such as when performing a backtrace.
- The cache is a splay tree along with a linked list for replacement.
- Each block caches a LINE_SIZE area of memory. Within each line we
- remember the address of the line (which must be a multiple of
- LINE_SIZE) and the actual data block.
- Lines are only allocated as needed, so DCACHE_SIZE really specifies the
- *maximum* number of lines in the cache.
- At present, the cache is write-through rather than writeback: as soon
- as data is written to the cache, it is also immediately written to
- the target. Therefore, cache lines are never "dirty". Whether a given
- line is valid or not depends on where it is stored in the dcache_struct;
- there is no per-block valid flag. */
- /* NOTE: Interaction of dcache and memory region attributes
- As there is no requirement that memory region attributes be aligned
- to or be a multiple of the dcache page size, dcache_read_line() and
- dcache_write_line() must break up the page by memory region. If a
- chunk does not have the cache attribute set, an invalid memory type
- is set, etc., then the chunk is skipped. Those chunks are handled
- in target_xfer_memory() (or target_xfer_memory_partial()).
- This doesn't occur very often. The most common occurance is when
- the last bit of the .text segment and the first bit of the .data
- segment fall within the same dcache page with a ro/cacheable memory
- region defined for the .text segment and a rw/non-cacheable memory
- region defined for the .data segment. */
- /* The maximum number of lines stored. The total size of the cache is
- equal to DCACHE_SIZE times LINE_SIZE. */
- #define DCACHE_DEFAULT_SIZE 4096
- static unsigned dcache_size = DCACHE_DEFAULT_SIZE;
- /* The default size of a cache line. Smaller values reduce the time taken to
- read a single byte and make the cache more granular, but increase
- overhead and reduce the effectiveness of the cache as a prefetcher. */
- #define DCACHE_DEFAULT_LINE_SIZE 64
- static unsigned dcache_line_size = DCACHE_DEFAULT_LINE_SIZE;
- /* Each cache block holds LINE_SIZE bytes of data
- starting at a multiple-of-LINE_SIZE address. */
- #define LINE_SIZE_MASK(dcache) ((dcache->line_size - 1))
- #define XFORM(dcache, x) ((x) & LINE_SIZE_MASK (dcache))
- #define MASK(dcache, x) ((x) & ~LINE_SIZE_MASK (dcache))
- struct dcache_block
- {
- /* For least-recently-allocated and free lists. */
- struct dcache_block *prev;
- struct dcache_block *next;
- CORE_ADDR addr; /* address of data */
- int refs; /* # hits */
- gdb_byte data[1]; /* line_size bytes at given address */
- };
- struct dcache_struct
- {
- splay_tree tree;
- struct dcache_block *oldest; /* least-recently-allocated list. */
- /* The free list is maintained identically to OLDEST to simplify
- the code: we only need one set of accessors. */
- struct dcache_block *freelist;
- /* The number of in-use lines in the cache. */
- int size;
- CORE_ADDR line_size; /* current line_size. */
- /* The ptid of last inferior to use cache or null_ptid. */
- ptid_t ptid;
- };
- typedef void (block_func) (struct dcache_block *block, void *param);
- static struct dcache_block *dcache_hit (DCACHE *dcache, CORE_ADDR addr);
- static int dcache_read_line (DCACHE *dcache, struct dcache_block *db);
- static struct dcache_block *dcache_alloc (DCACHE *dcache, CORE_ADDR addr);
- static void dcache_info (char *exp, int tty);
- void _initialize_dcache (void);
- static int dcache_enabled_p = 0; /* OBSOLETE */
- static void
- show_dcache_enabled_p (struct ui_file *file, int from_tty,
- struct cmd_list_element *c, const char *value)
- {
- fprintf_filtered (file, _("Deprecated remotecache flag is %s.\n"), value);
- }
- /* Add BLOCK to circular block list BLIST, behind the block at *BLIST.
- *BLIST is not updated (unless it was previously NULL of course).
- This is for the least-recently-allocated list's sake:
- BLIST points to the oldest block.
- ??? This makes for poor cache usage of the free list,
- but is it measurable? */
- static void
- append_block (struct dcache_block **blist, struct dcache_block *block)
- {
- if (*blist)
- {
- block->next = *blist;
- block->prev = (*blist)->prev;
- block->prev->next = block;
- (*blist)->prev = block;
- /* We don't update *BLIST here to maintain the invariant that for the
- least-recently-allocated list *BLIST points to the oldest block. */
- }
- else
- {
- block->next = block;
- block->prev = block;
- *blist = block;
- }
- }
- /* Remove BLOCK from circular block list BLIST. */
- static void
- remove_block (struct dcache_block **blist, struct dcache_block *block)
- {
- if (block->next == block)
- {
- *blist = NULL;
- }
- else
- {
- block->next->prev = block->prev;
- block->prev->next = block->next;
- /* If we removed the block *BLIST points to, shift it to the next block
- to maintain the invariant that for the least-recently-allocated list
- *BLIST points to the oldest block. */
- if (*blist == block)
- *blist = block->next;
- }
- }
- /* Iterate over all elements in BLIST, calling FUNC.
- PARAM is passed to FUNC.
- FUNC may remove the block it's passed, but only that block. */
- static void
- for_each_block (struct dcache_block **blist, block_func *func, void *param)
- {
- struct dcache_block *db;
- if (*blist == NULL)
- return;
- db = *blist;
- do
- {
- struct dcache_block *next = db->next;
- func (db, param);
- db = next;
- }
- while (*blist && db != *blist);
- }
- /* BLOCK_FUNC routine for dcache_free. */
- static void
- free_block (struct dcache_block *block, void *param)
- {
- xfree (block);
- }
- /* Free a data cache. */
- void
- dcache_free (DCACHE *dcache)
- {
- splay_tree_delete (dcache->tree);
- for_each_block (&dcache->oldest, free_block, NULL);
- for_each_block (&dcache->freelist, free_block, NULL);
- xfree (dcache);
- }
- /* BLOCK_FUNC function for dcache_invalidate.
- This doesn't remove the block from the oldest list on purpose.
- dcache_invalidate will do it later. */
- static void
- invalidate_block (struct dcache_block *block, void *param)
- {
- DCACHE *dcache = (DCACHE *) param;
- splay_tree_remove (dcache->tree, (splay_tree_key) block->addr);
- append_block (&dcache->freelist, block);
- }
- /* Free all the data cache blocks, thus discarding all cached data. */
- void
- dcache_invalidate (DCACHE *dcache)
- {
- for_each_block (&dcache->oldest, invalidate_block, dcache);
- dcache->oldest = NULL;
- dcache->size = 0;
- dcache->ptid = null_ptid;
- if (dcache->line_size != dcache_line_size)
- {
- /* We've been asked to use a different line size.
- All of our freelist blocks are now the wrong size, so free them. */
- for_each_block (&dcache->freelist, free_block, dcache);
- dcache->freelist = NULL;
- dcache->line_size = dcache_line_size;
- }
- }
- /* Invalidate the line associated with ADDR. */
- static void
- dcache_invalidate_line (DCACHE *dcache, CORE_ADDR addr)
- {
- struct dcache_block *db = dcache_hit (dcache, addr);
- if (db)
- {
- splay_tree_remove (dcache->tree, (splay_tree_key) db->addr);
- remove_block (&dcache->oldest, db);
- append_block (&dcache->freelist, db);
- --dcache->size;
- }
- }
- /* If addr is present in the dcache, return the address of the block
- containing it. Otherwise return NULL. */
- static struct dcache_block *
- dcache_hit (DCACHE *dcache, CORE_ADDR addr)
- {
- struct dcache_block *db;
- splay_tree_node node = splay_tree_lookup (dcache->tree,
- (splay_tree_key) MASK (dcache, addr));
- if (!node)
- return NULL;
- db = (struct dcache_block *) node->value;
- db->refs++;
- return db;
- }
- /* Fill a cache line from target memory.
- The result is 1 for success, 0 if the (entire) cache line
- wasn't readable. */
- static int
- dcache_read_line (DCACHE *dcache, struct dcache_block *db)
- {
- CORE_ADDR memaddr;
- gdb_byte *myaddr;
- int len;
- int res;
- int reg_len;
- struct mem_region *region;
- len = dcache->line_size;
- memaddr = db->addr;
- myaddr = db->data;
- while (len > 0)
- {
- /* Don't overrun if this block is right at the end of the region. */
- region = lookup_mem_region (memaddr);
- if (region->hi == 0 || memaddr + len < region->hi)
- reg_len = len;
- else
- reg_len = region->hi - memaddr;
- /* Skip non-readable regions. The cache attribute can be ignored,
- since we may be loading this for a stack access. */
- if (region->attrib.mode == MEM_WO)
- {
- memaddr += reg_len;
- myaddr += reg_len;
- len -= reg_len;
- continue;
- }
- res = target_read_raw_memory (memaddr, myaddr, reg_len);
- if (res != 0)
- return 0;
- memaddr += reg_len;
- myaddr += reg_len;
- len -= reg_len;
- }
- return 1;
- }
- /* Get a free cache block, put or keep it on the valid list,
- and return its address. */
- static struct dcache_block *
- dcache_alloc (DCACHE *dcache, CORE_ADDR addr)
- {
- struct dcache_block *db;
- if (dcache->size >= dcache_size)
- {
- /* Evict the least recently allocated line. */
- db = dcache->oldest;
- remove_block (&dcache->oldest, db);
- splay_tree_remove (dcache->tree, (splay_tree_key) db->addr);
- }
- else
- {
- db = dcache->freelist;
- if (db)
- remove_block (&dcache->freelist, db);
- else
- db = xmalloc (offsetof (struct dcache_block, data) +
- dcache->line_size);
- dcache->size++;
- }
- db->addr = MASK (dcache, addr);
- db->refs = 0;
- /* Put DB at the end of the list, it's the newest. */
- append_block (&dcache->oldest, db);
- splay_tree_insert (dcache->tree, (splay_tree_key) db->addr,
- (splay_tree_value) db);
- return db;
- }
- /* Using the data cache DCACHE, store in *PTR the contents of the byte at
- address ADDR in the remote machine.
- Returns 1 for success, 0 for error. */
- static int
- dcache_peek_byte (DCACHE *dcache, CORE_ADDR addr, gdb_byte *ptr)
- {
- struct dcache_block *db = dcache_hit (dcache, addr);
- if (!db)
- {
- db = dcache_alloc (dcache, addr);
- if (!dcache_read_line (dcache, db))
- return 0;
- }
- *ptr = db->data[XFORM (dcache, addr)];
- return 1;
- }
- /* Write the byte at PTR into ADDR in the data cache.
- The caller should have written the data through to target memory
- already.
- If ADDR is not in cache, this function does nothing; writing to an
- area of memory which wasn't present in the cache doesn't cause it
- to be loaded in. */
- static void
- dcache_poke_byte (DCACHE *dcache, CORE_ADDR addr, const gdb_byte *ptr)
- {
- struct dcache_block *db = dcache_hit (dcache, addr);
- if (db)
- db->data[XFORM (dcache, addr)] = *ptr;
- }
- static int
- dcache_splay_tree_compare (splay_tree_key a, splay_tree_key b)
- {
- if (a > b)
- return 1;
- else if (a == b)
- return 0;
- else
- return -1;
- }
- /* Allocate and initialize a data cache. */
- DCACHE *
- dcache_init (void)
- {
- DCACHE *dcache;
- dcache = (DCACHE *) xmalloc (sizeof (*dcache));
- dcache->tree = splay_tree_new (dcache_splay_tree_compare,
- NULL,
- NULL);
- dcache->oldest = NULL;
- dcache->freelist = NULL;
- dcache->size = 0;
- dcache->line_size = dcache_line_size;
- dcache->ptid = null_ptid;
- return dcache;
- }
- /* Read LEN bytes from dcache memory at MEMADDR, transferring to
- debugger address MYADDR. If the data is presently cached, this
- fills the cache. Arguments/return are like the target_xfer_partial
- interface. */
- enum target_xfer_status
- dcache_read_memory_partial (struct target_ops *ops, DCACHE *dcache,
- CORE_ADDR memaddr, gdb_byte *myaddr,
- ULONGEST len, ULONGEST *xfered_len)
- {
- ULONGEST i;
- /* If this is a different inferior from what we've recorded,
- flush the cache. */
- if (! ptid_equal (inferior_ptid, dcache->ptid))
- {
- dcache_invalidate (dcache);
- dcache->ptid = inferior_ptid;
- }
- for (i = 0; i < len; i++)
- {
- if (!dcache_peek_byte (dcache, memaddr + i, myaddr + i))
- {
- /* That failed. Discard its cache line so we don't have a
- partially read line. */
- dcache_invalidate_line (dcache, memaddr + i);
- break;
- }
- }
- if (i == 0)
- {
- /* Even though reading the whole line failed, we may be able to
- read a piece starting where the caller wanted. */
- return ops->to_xfer_partial (ops, TARGET_OBJECT_MEMORY, NULL,
- myaddr, NULL, memaddr, len,
- xfered_len);
- }
- else
- {
- *xfered_len = i;
- return TARGET_XFER_OK;
- }
- }
- /* FIXME: There would be some benefit to making the cache write-back and
- moving the writeback operation to a higher layer, as it could occur
- after a sequence of smaller writes have been completed (as when a stack
- frame is constructed for an inferior function call). Note that only
- moving it up one level to target_xfer_memory[_partial]() is not
- sufficient since we want to coalesce memory transfers that are
- "logically" connected but not actually a single call to one of the
- memory transfer functions. */
- /* Just update any cache lines which are already present. This is
- called by the target_xfer_partial machinery when writing raw
- memory. */
- void
- dcache_update (DCACHE *dcache, enum target_xfer_status status,
- CORE_ADDR memaddr, const gdb_byte *myaddr,
- ULONGEST len)
- {
- ULONGEST i;
- for (i = 0; i < len; i++)
- if (status == TARGET_XFER_OK)
- dcache_poke_byte (dcache, memaddr + i, myaddr + i);
- else
- {
- /* Discard the whole cache line so we don't have a partially
- valid line. */
- dcache_invalidate_line (dcache, memaddr + i);
- }
- }
- /* Print DCACHE line INDEX. */
- static void
- dcache_print_line (DCACHE *dcache, int index)
- {
- splay_tree_node n;
- struct dcache_block *db;
- int i, j;
- if (dcache == NULL)
- {
- printf_filtered (_("No data cache available.\n"));
- return;
- }
- n = splay_tree_min (dcache->tree);
- for (i = index; i > 0; --i)
- {
- if (!n)
- break;
- n = splay_tree_successor (dcache->tree, n->key);
- }
- if (!n)
- {
- printf_filtered (_("No such cache line exists.\n"));
- return;
- }
- db = (struct dcache_block *) n->value;
- printf_filtered (_("Line %d: address %s [%d hits]\n"),
- index, paddress (target_gdbarch (), db->addr), db->refs);
- for (j = 0; j < dcache->line_size; j++)
- {
- printf_filtered ("%02x ", db->data[j]);
- /* Print a newline every 16 bytes (48 characters). */
- if ((j % 16 == 15) && (j != dcache->line_size - 1))
- printf_filtered ("\n");
- }
- printf_filtered ("\n");
- }
- /* Parse EXP and show the info about DCACHE. */
- static void
- dcache_info_1 (DCACHE *dcache, char *exp)
- {
- splay_tree_node n;
- int i, refcount;
- if (exp)
- {
- char *linestart;
- i = strtol (exp, &linestart, 10);
- if (linestart == exp || i < 0)
- {
- printf_filtered (_("Usage: info dcache [linenumber]\n"));
- return;
- }
- dcache_print_line (dcache, i);
- return;
- }
- printf_filtered (_("Dcache %u lines of %u bytes each.\n"),
- dcache_size,
- dcache ? (unsigned) dcache->line_size
- : dcache_line_size);
- if (dcache == NULL || ptid_equal (dcache->ptid, null_ptid))
- {
- printf_filtered (_("No data cache available.\n"));
- return;
- }
- printf_filtered (_("Contains data for %s\n"),
- target_pid_to_str (dcache->ptid));
- refcount = 0;
- n = splay_tree_min (dcache->tree);
- i = 0;
- while (n)
- {
- struct dcache_block *db = (struct dcache_block *) n->value;
- printf_filtered (_("Line %d: address %s [%d hits]\n"),
- i, paddress (target_gdbarch (), db->addr), db->refs);
- i++;
- refcount += db->refs;
- n = splay_tree_successor (dcache->tree, n->key);
- }
- printf_filtered (_("Cache state: %d active lines, %d hits\n"), i, refcount);
- }
- static void
- dcache_info (char *exp, int tty)
- {
- dcache_info_1 (target_dcache_get (), exp);
- }
- static void
- set_dcache_size (char *args, int from_tty,
- struct cmd_list_element *c)
- {
- if (dcache_size == 0)
- {
- dcache_size = DCACHE_DEFAULT_SIZE;
- error (_("Dcache size must be greater than 0."));
- }
- target_dcache_invalidate ();
- }
- static void
- set_dcache_line_size (char *args, int from_tty,
- struct cmd_list_element *c)
- {
- if (dcache_line_size < 2
- || (dcache_line_size & (dcache_line_size - 1)) != 0)
- {
- unsigned d = dcache_line_size;
- dcache_line_size = DCACHE_DEFAULT_LINE_SIZE;
- error (_("Invalid dcache line size: %u (must be power of 2)."), d);
- }
- target_dcache_invalidate ();
- }
- static void
- set_dcache_command (char *arg, int from_tty)
- {
- printf_unfiltered (
- "\"set dcache\" must be followed by the name of a subcommand.\n");
- help_list (dcache_set_list, "set dcache ", all_commands, gdb_stdout);
- }
- static void
- show_dcache_command (char *args, int from_tty)
- {
- cmd_show_list (dcache_show_list, from_tty, "");
- }
- void
- _initialize_dcache (void)
- {
- add_setshow_boolean_cmd ("remotecache", class_support,
- &dcache_enabled_p, _("\
- Set cache use for remote targets."), _("\
- Show cache use for remote targets."), _("\
- This used to enable the data cache for remote targets. The cache\n\
- functionality is now controlled by the memory region system and the\n\
- \"stack-cache\" flag; \"remotecache\" now does nothing and\n\
- exists only for compatibility reasons."),
- NULL,
- show_dcache_enabled_p,
- &setlist, &showlist);
- add_info ("dcache", dcache_info,
- _("\
- Print information on the dcache performance.\n\
- With no arguments, this command prints the cache configuration and a\n\
- summary of each line in the cache. Use \"info dcache <lineno> to dump\"\n\
- the contents of a given line."));
- add_prefix_cmd ("dcache", class_obscure, set_dcache_command, _("\
- Use this command to set number of lines in dcache and line-size."),
- &dcache_set_list, "set dcache ", /*allow_unknown*/0, &setlist);
- add_prefix_cmd ("dcache", class_obscure, show_dcache_command, _("\
- Show dcachesettings."),
- &dcache_show_list, "show dcache ", /*allow_unknown*/0, &showlist);
- add_setshow_zuinteger_cmd ("line-size", class_obscure,
- &dcache_line_size, _("\
- Set dcache line size in bytes (must be power of 2)."), _("\
- Show dcache line size."),
- NULL,
- set_dcache_line_size,
- NULL,
- &dcache_set_list, &dcache_show_list);
- add_setshow_zuinteger_cmd ("size", class_obscure,
- &dcache_size, _("\
- Set number of dcache lines."), _("\
- Show number of dcache lines."),
- NULL,
- set_dcache_size,
- NULL,
- &dcache_set_list, &dcache_show_list);
- }