/* Target memory searching

  Copyright (C) 2020-2024 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 "gdbsupport/search.h"
#include "gdbsupport/byte-vector.h"

/* This implements a basic search of memory, reading target memory and
  performing the search here (as opposed to performing the search in on the
  target side with, for example, gdbserver).  */

int
simple_search_memory
 (gdb::function_view<target_read_memory_ftype> read_memory,
  CORE_ADDR start_addr, ULONGEST search_space_len,
  const gdb_byte *pattern, ULONGEST pattern_len,
  CORE_ADDR *found_addrp)
{
 const unsigned chunk_size = SEARCH_CHUNK_SIZE;
 /* Buffer to hold memory contents for searching.  */
 unsigned search_buf_size;

 search_buf_size = chunk_size + pattern_len - 1;

 /* No point in trying to allocate a buffer larger than the search space.  */
 if (search_space_len < search_buf_size)
   search_buf_size = search_space_len;

 gdb::byte_vector search_buf (search_buf_size);

 /* Prime the search buffer.  */

 if (!read_memory (start_addr, search_buf.data (), search_buf_size))
   {
     warning (_("Unable to access %s bytes of target "
                "memory at %s, halting search."),
              pulongest (search_buf_size), hex_string (start_addr));
     return -1;
   }

 /* Perform the search.

    The loop is kept simple by allocating [N + pattern-length - 1] bytes.
    When we've scanned N bytes we copy the trailing bytes to the start and
    read in another N bytes.  */

 while (search_space_len >= pattern_len)
   {
     gdb_byte *found_ptr;
     unsigned nr_search_bytes
       = std::min (search_space_len, (ULONGEST) search_buf_size);

     found_ptr = (gdb_byte *) memmem (search_buf.data (), nr_search_bytes,
                                      pattern, pattern_len);

     if (found_ptr != NULL)
       {
         CORE_ADDR found_addr = start_addr + (found_ptr - search_buf.data ());

         *found_addrp = found_addr;
         return 1;
       }

     /* Not found in this chunk, skip to next chunk.  */

     /* Don't let search_space_len wrap here, it's unsigned.  */
     if (search_space_len >= chunk_size)
       search_space_len -= chunk_size;
     else
       search_space_len = 0;

     if (search_space_len >= pattern_len)
       {
         unsigned keep_len = search_buf_size - chunk_size;
         CORE_ADDR read_addr = start_addr + chunk_size + keep_len;
         int nr_to_read;

         /* Copy the trailing part of the previous iteration to the front
            of the buffer for the next iteration.  */
         gdb_assert (keep_len == pattern_len - 1);
         if (keep_len > 0)
           memcpy (&search_buf[0], &search_buf[chunk_size], keep_len);

         nr_to_read = std::min (search_space_len - keep_len,
                                (ULONGEST) chunk_size);

         if (!read_memory (read_addr, &search_buf[keep_len], nr_to_read))
           {
             warning (_("Unable to access %s bytes of target "
                        "memory at %s, halting search."),
                      plongest (nr_to_read),
                      hex_string (read_addr));
             return -1;
           }

         start_addr += chunk_size;
       }
   }

 /* Not found.  */

 return 0;
}