diff mbox series

[6/6] cmd: Add a memory-search command

Message ID 20200602192642.6.Iaf24bd607de3836b8cd474fd0ae88452396ccb62@changeid
State Accepted
Commit bdded2015c1e54038a86557e339b606b4a31968b
Headers show
Series [1/6] Update MEM_SUPPORT_64BIT_DATA to be always defined | expand

Commit Message

Simon Glass June 3, 2020, 1:26 a.m. UTC
It is useful to be able to find hex values and strings in a memory range.
Add a command to support this.

cmd: Fix 'md' and add a memory-search command
At present 'md.q' is broken. This series provides a fix for this. It also
implements a new memory-search command called 'ms'. It allows searching
memory for hex and string data.
END

Signed-off-by: Simon Glass <sjg at chromium.org>
---

 README                |  10 ++
 cmd/Kconfig           |  14 +++
 cmd/mem.c             | 151 +++++++++++++++++++++++
 test/Makefile         |   1 +
 test/cmd/Makefile     |   5 +
 test/cmd/mem_search.c | 275 ++++++++++++++++++++++++++++++++++++++++++
 6 files changed, 456 insertions(+)
 create mode 100644 test/cmd/Makefile
 create mode 100644 test/cmd/mem_search.c

Comments

Stefan Roese June 3, 2020, 6:05 a.m. UTC | #1
On 03.06.20 03:26, Simon Glass wrote:
> It is useful to be able to find hex values and strings in a memory range.
> Add a command to support this.
> 
> cmd: Fix 'md' and add a memory-search command
> At present 'md.q' is broken. This series provides a fix for this.

Perhaps this commit text belongs to a patchset cover letter? It does
not really match this explicit patch.

BTW: Why is md.q broken? I'm using it on my Octeon 64bit platform
without any issues so far.

> It also
> implements a new memory-search command called 'ms'. It allows searching
> memory for hex and string data.
> END
> 
> Signed-off-by: Simon Glass <sjg at chromium.org>

Some minor review comments below.

> ---
> 
>   README                |  10 ++
>   cmd/Kconfig           |  14 +++
>   cmd/mem.c             | 151 +++++++++++++++++++++++
>   test/Makefile         |   1 +
>   test/cmd/Makefile     |   5 +
>   test/cmd/mem_search.c | 275 ++++++++++++++++++++++++++++++++++++++++++
>   6 files changed, 456 insertions(+)
>   create mode 100644 test/cmd/Makefile
>   create mode 100644 test/cmd/mem_search.c
> 
> diff --git a/README b/README
> index 17dc0ee33b..8abcff0783 100644
> --- a/README
> +++ b/README
> @@ -3283,6 +3283,7 @@ md	- memory display
>   mm	- memory modify (auto-incrementing)
>   nm	- memory modify (constant address)
>   mw	- memory write (fill)
> +ms	- memory search
>   cp	- memory copy
>   cmp	- memory compare
>   crc32	- checksum calculation
> @@ -3528,6 +3529,15 @@ List of environment variables (most likely not complete):
>   		  CONFIG_NET_RETRY_COUNT, if defined. This value has
>   		  precedence over the valu based on CONFIG_NET_RETRY_COUNT.
>   
> +  memmatches	- Number of matches found by the last 'ms' command, in hex
> +
> +  memaddr	- Address of the last match found by the 'ms' command, in hex,
> +		  or 0 if none
> +
> +  mempos	- Index position of the last match found by the 'ms' command,
> +		  in units of the size (.b, .w, .l) of the search
> +
> +
>   The following image location variables contain the location of images
>   used in booting. The "Image" column gives the role of the image and is
>   not an environment variable name. The other columns are environment
> diff --git a/cmd/Kconfig b/cmd/Kconfig
> index 153864c587..a02a376d49 100644
> --- a/cmd/Kconfig
> +++ b/cmd/Kconfig
> @@ -717,6 +717,20 @@ config CMD_MEMORY
>   	    base - print or set address offset
>   	    loop - initialize loop on address range
>   
> +config MEM_SEARCH
> +	bool "ms - Memory search"
> +	help
> +	  Memory-search command
> +
> +	  This allows searching through a region of memory looking for hex
> +	  data (byte, 16-bit word, 32-bit long, also 64-bit on machines that
> +	  support it). It is also possible to search for a string. The
> +	  command accepts a memory range and a list of values to search for.
> +	  The values need to appear in memory in the same order they are given
> +	  in the command. At most 10 matches can be returned at a time, but
> +	  pressing return will show the next 10 matches. Environment variables
> +	  are set for use with scripting (memmatches, memaddr, mempos).
> +
>   config CMD_MX_CYCLIC
>   	bool "Enable cyclic md/mw commands"
>   	depends on CMD_MEMORY
> diff --git a/cmd/mem.c b/cmd/mem.c
> index 9ab6b1dd08..575893c18d 100644
> --- a/cmd/mem.c
> +++ b/cmd/mem.c
> @@ -25,6 +25,7 @@
>   #include <asm/io.h>
>   #include <linux/bitops.h>
>   #include <linux/compiler.h>
> +#include <linux/ctype.h>
>   #include <linux/delay.h>
>   
>   DECLARE_GLOBAL_DATA_PTR;
> @@ -52,6 +53,10 @@ static ulong	dp_last_length = 0x40;
>   static ulong	mm_last_addr, mm_last_size;
>   
>   static	ulong	base_address = 0;
> +#ifdef CONFIG_MEM_SEARCH
> +static u8 search_buf[64];
> +static uint search_len;
> +#endif
>   
>   /* Memory Display
>    *
> @@ -362,6 +367,142 @@ static int do_mem_cp(struct cmd_tbl *cmdtp, int flag, int argc,
>   	return 0;
>   }
>   
> +#ifdef CONFIG_MEM_SEARCH
> +static int do_mem_search(struct cmd_tbl *cmdtp, int flag, int argc,
> +			 char *const argv[])
> +{
> +	ulong addr, length, bytes, offset;
> +	u8 *ptr, *end, *buf;
> +	bool quiet = false;
> +	ulong last_pos;		/* Offset of last match in 'size' units*/
> +	ulong last_addr;	/* Address of last displayed line */
> +	int limit = 10;
> +	int count;
> +	int size;
> +	int i;
> +
> +	/* We use the last specified parameters, unless new ones are entered */
> +	addr = dp_last_addr;
> +	size = dp_last_size;
> +	length = dp_last_length;
> +
> +	if (argc < 3)
> +		return CMD_RET_USAGE;
> +
> +	if ((!flag & CMD_FLAG_REPEAT)) {
> +		/*
> +		 * Check for a size specification.
> +		 * Defaults to long if no or incorrect specification.
> +		 */
> +		size = cmd_get_data_size(argv[0], 4);
> +		if (size < 0 && size != -2 /* string */)
> +			return 1;
> +
> +		argc--; argv++;

Two assignments in one line? ...

> +		while (argc && *argv[0] == '-') {
> +			int ch = argv[0][1];
> +
> +			if (ch == 'q')
> +				quiet = true;
> +			else if (ch == 'l' && isxdigit(argv[0][2]))
> +				limit = simple_strtoul(argv[0] + 2, NULL, 16);
> +			else
> +				return CMD_RET_USAGE;
> +			argc--; argv++;

... here as well.

Thanks,
Stefan

> +		}
> +
> +		/* Address is specified since argc > 1 */
> +		addr = simple_strtoul(argv[0], NULL, 16);
> +		addr += base_address;
> +
> +		/* Length is the number of objects, not number of bytes */
> +		length = simple_strtoul(argv[1], NULL, 16);
> +
> +		/* Read the bytes to search for */
> +		end = search_buf + sizeof(search_buf);
> +		for (i = 2, ptr = search_buf; i < argc && ptr < end; i++) {
> +			if (SUPPORT_64BIT_DATA && size == 8) {
> +				u64 val = simple_strtoull(argv[i], NULL, 16);
> +
> +				*(u64 *)ptr = val;
> +			} else if (size == -2) {  /* string */
> +				int len = min(strlen(argv[i]),
> +					      (size_t)(end - ptr));
> +
> +				memcpy(ptr, argv[i], len);
> +				ptr += len;
> +				continue;
> +			} else {
> +				u32 val = simple_strtoul(argv[i], NULL, 16);
> +
> +				switch (size) {
> +				case 1:
> +					*ptr = val;
> +					break;
> +				case 2:
> +					*(u16 *)ptr = val;
> +					break;
> +				case 4:
> +					*(u32 *)ptr = val;
> +					break;
> +				}
> +			}
> +			ptr += size;
> +		}
> +		search_len = ptr - search_buf;
> +	}
> +
> +	/* Do the search */
> +	if (size == -2)
> +		size = 1;
> +	bytes = size * length;
> +	buf = map_sysmem(addr, bytes);
> +	last_pos = 0;
> +	last_addr = 0;
> +	count = 0;
> +	for (offset = 0; offset <= bytes - search_len && count < limit;
> +	     offset += size) {
> +		void *ptr = buf + offset;
> +
> +		if (!memcmp(ptr, search_buf, search_len)) {
> +			uint align = (addr + offset) & 0xf;
> +			ulong match = addr + offset;
> +
> +			if (!count || (last_addr & ~0xf) != (match & ~0xf)) {
> +				if (!quiet) {
> +					if (count)
> +						printf("--\n");
> +					print_buffer(match - align, ptr - align,
> +						     size,
> +						     ALIGN(search_len + align,
> +							   16) / size, 0);
> +				}
> +				last_addr = match;
> +				last_pos = offset / size;
> +			}
> +			count++;
> +		}
> +	}
> +	if (!quiet) {
> +		printf("%d match%s", count, count == 1 ? "" : "es");
> +		if (count == limit)
> +			printf(" (repeat command to check for more)");
> +		printf("\n");
> +	}
> +	env_set_hex("memmatches", count);
> +	env_set_hex("memaddr", last_addr);
> +	env_set_hex("mempos", last_pos);
> +
> +	unmap_sysmem(buf);
> +
> +	dp_last_addr = addr + offset / size;
> +	dp_last_size = size;
> +	dp_last_length = length - offset / size;
> +
> +	return count ? 0 : CMD_RET_FAILURE;
> +}
> +#endif
> +
>   static int do_mem_base(struct cmd_tbl *cmdtp, int flag, int argc,
>   		       char *const argv[])
>   {
> @@ -1196,6 +1337,16 @@ U_BOOT_CMD(
>   	"[.b, .w, .l" HELP_Q "] addr1 addr2 count"
>   );
>   
> +#ifdef CONFIG_MEM_SEARCH
> +/**************************************************/
> +U_BOOT_CMD(
> +	ms,	255,	1,	do_mem_search,
> +	"memory search",
> +	"[.b, .w, .l" HELP_Q ", .s] [-q | -<n>] address #-of-objects <value>..."
> +	"  -q = quiet, -l<val> = match limit" :
> +);
> +#endif
> +
>   #ifdef CONFIG_CMD_CRC32
>   
>   #ifndef CONFIG_CRC32_VERIFY
> diff --git a/test/Makefile b/test/Makefile
> index bab8f1a5c2..7c4039964e 100644
> --- a/test/Makefile
> +++ b/test/Makefile
> @@ -3,6 +3,7 @@
>   # (C) Copyright 2012 The Chromium Authors
>   
>   obj-$(CONFIG_SANDBOX) += bloblist.o
> +obj-$(CONFIG_CMDLINE) += cmd/
>   obj-$(CONFIG_UNIT_TEST) += cmd_ut.o
>   obj-$(CONFIG_UNIT_TEST) += ut.o
>   obj-$(CONFIG_SANDBOX) += command_ut.o
> diff --git a/test/cmd/Makefile b/test/cmd/Makefile
> new file mode 100644
> index 0000000000..85d38f09e8
> --- /dev/null
> +++ b/test/cmd/Makefile
> @@ -0,0 +1,5 @@
> +# SPDX-License-Identifier: GPL-2.0+
> +#
> +# Copyright (c) 2013 Google, Inc
> +
> +obj-$(CONFIG_MEM_SEARCH) += mem_search.o
> diff --git a/test/cmd/mem_search.c b/test/cmd/mem_search.c
> new file mode 100644
> index 0000000000..d57bfad398
> --- /dev/null
> +++ b/test/cmd/mem_search.c
> @@ -0,0 +1,275 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Tests for memory commands
> + *
> + * Copyright 2020 Google LLC
> + * Written by Simon Glass <sjg at chromium.org>
> + */
> +
> +#include <common.h>
> +#include <console.h>
> +#include <mapmem.h>
> +#include <dm/test.h>
> +#include <test/ut.h>
> +
> +#define BUF_SIZE	0x100
> +
> +/* Test 'ms' command with bytes */
> +static int dm_test_ms_b(struct unit_test_state *uts)
> +{
> +	u8 *buf;
> +
> +	buf = map_sysmem(0, BUF_SIZE + 1);
> +	memset(buf, '\0', BUF_SIZE);
> +	buf[0x0] = 0x12;
> +	buf[0x31] = 0x12;
> +	buf[0xff] = 0x12;
> +	buf[0x100] = 0x12;
> +	console_record_reset();
> +	run_command("ms.b 1 ff 12", 0);
> +	ut_assert_nextline("00000030: 00 12 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................");
> +	ut_assert_nextline("--");
> +	ut_assert_nextline("000000f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 12    ................");
> +	ut_assert_nextline("2 matches");
> +	ut_assert_console_end();
> +
> +	ut_asserteq(2, env_get_hex("memmatches", 0));
> +	ut_asserteq(0xff, env_get_hex("memaddr", 0));
> +	ut_asserteq(0xfe, env_get_hex("mempos", 0));
> +
> +	unmap_sysmem(buf);
> +
> +	return 0;
> +}
> +DM_TEST(dm_test_ms_b, 0);
> +
> +/* Test 'ms' command with 16-bit values */
> +static int dm_test_ms_w(struct unit_test_state *uts)
> +{
> +	u16 *buf;
> +
> +	buf = map_sysmem(0, BUF_SIZE + 2);
> +	memset(buf, '\0', BUF_SIZE);
> +	buf[0x34 / 2] = 0x1234;
> +	buf[BUF_SIZE / 2] = 0x1234;
> +	console_record_reset();
> +	run_command("ms.w 0 80 1234", 0);
> +	ut_assert_nextline("00000030: 0000 0000 1234 0000 0000 0000 0000 0000    ....4...........");
> +	ut_assert_nextline("1 match");
> +	ut_assert_console_end();
> +
> +	ut_asserteq(1, env_get_hex("memmatches", 0));
> +	ut_asserteq(0x34, env_get_hex("memaddr", 0));
> +	ut_asserteq(0x34 / 2, env_get_hex("mempos", 0));
> +
> +	unmap_sysmem(buf);
> +
> +	return 0;
> +}
> +DM_TEST(dm_test_ms_w, 0);
> +
> +/* Test 'ms' command with 32-bit values */
> +static int dm_test_ms_l(struct unit_test_state *uts)
> +{
> +	u32 *buf;
> +
> +	buf = map_sysmem(0, BUF_SIZE + 4);
> +	memset(buf, '\0', BUF_SIZE);
> +	buf[0x38 / 4] = 0x12345678;
> +	buf[BUF_SIZE / 4] = 0x12345678;
> +	console_record_reset();
> +	run_command("ms 0 40 12345678", 0);
> +	ut_assert_nextline("00000030: 00000000 00000000 12345678 00000000    ........xV4.....");
> +	ut_assert_nextline("1 match");
> +	ut_assert_console_end();
> +
> +	ut_asserteq(1, env_get_hex("memmatches", 0));
> +	ut_asserteq(0x38, env_get_hex("memaddr", 0));
> +	ut_asserteq(0x38 / 4, env_get_hex("mempos", 0));
> +
> +	console_record_reset();
> +	run_command("ms 0 80 12345679", 0);
> +	ut_assert_nextline("0 matches");
> +	ut_assert_console_end();
> +
> +	ut_asserteq(0, env_get_hex("memmatches", 0));
> +	ut_asserteq(0, env_get_hex("memaddr", 0));
> +	ut_asserteq(0 / 4, env_get_hex("mempos", 0));
> +
> +	unmap_sysmem(buf);
> +
> +	return 0;
> +}
> +DM_TEST(dm_test_ms_l, 0);
> +
> +/* Test 'ms' command with continuation */
> +static int dm_test_ms_cont(struct unit_test_state *uts)
> +{
> +	char *const args[] = {"ms.b", "0", "100", "34"};
> +	int repeatable;
> +	u8 *buf;
> +	int i;
> +
> +	buf = map_sysmem(0, BUF_SIZE);
> +	memset(buf, '\0', BUF_SIZE);
> +	for (i = 5; i < 0x33; i += 3)
> +		buf[i] = 0x34;
> +	console_record_reset();
> +	run_command("ms.b 0 100 34", 0);
> +	ut_assert_nextlinen("00000000: 00 00 00 00 00 34 00 00 34 00 00 34 00 00 34 00");
> +	ut_assert_nextline("--");
> +	ut_assert_nextlinen("00000010: 00 34 00 00 34 00 00 34 00 00 34 00 00 34 00 00");
> +	ut_assert_nextline("--");
> +	ut_assert_nextlinen("00000020: 34 00 00 34 00 00 34 00 00 34 00 00 34 00 00 34");
> +	ut_assert_nextlinen("10 matches (repeat command to check for more)");
> +	ut_assert_console_end();
> +
> +	ut_asserteq(10, env_get_hex("memmatches", 0));
> +	ut_asserteq(0x20, env_get_hex("memaddr", 0));
> +	ut_asserteq(0x20, env_get_hex("mempos", 0));
> +
> +	/*
> +	 * run_command() ignoes the repeatable flag when using hush, so call
> +	 * cmd_process() directly
> +	 */
> +	console_record_reset();
> +	cmd_process(CMD_FLAG_REPEAT, 4, args, &repeatable, NULL);
> +	ut_assert_nextlinen("00000020: 34 00 00 34 00 00 34 00 00 34 00 00 34 00 00 34");
> +	ut_assert_nextline("--");
> +	ut_assert_nextlinen("00000030: 00 00 34 00 00 00 00 00");
> +	ut_assert_nextlinen("6 matches");
> +	ut_assert_console_end();
> +
> +	ut_asserteq(6, env_get_hex("memmatches", 0));
> +	ut_asserteq(0x32, env_get_hex("memaddr", 0));
> +
> +	/* 0x32 less 0x21, where the second search started */
> +	ut_asserteq(0x11, env_get_hex("mempos", 0));
> +
> +	unmap_sysmem(buf);
> +
> +	return 0;
> +}
> +DM_TEST(dm_test_ms_cont, 0);
> +
> +/* Test 'ms' command with multiple values */
> +static int dm_test_ms_mult(struct unit_test_state *uts)
> +{
> +	static const char str[] = "hello";
> +	char *buf;
> +
> +	buf = map_sysmem(0, BUF_SIZE + 5);
> +	memset(buf, '\0', BUF_SIZE);
> +	strcpy(buf + 0x1e, str);
> +	strcpy(buf + 0x63, str);
> +	strcpy(buf + BUF_SIZE - strlen(str) + 1, str);
> +	console_record_reset();
> +	run_command("ms.b 0 100 68 65 6c 6c 6f", 0);
> +	ut_assert_nextline("00000010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 68 65    ..............he");
> +	ut_assert_nextline("00000020: 6c 6c 6f 00 00 00 00 00 00 00 00 00 00 00 00 00    llo.............");
> +	ut_assert_nextline("--");
> +	ut_assert_nextline("00000060: 00 00 00 68 65 6c 6c 6f 00 00 00 00 00 00 00 00    ...hello........");
> +	ut_assert_nextline("2 matches");
> +	ut_assert_console_end();
> +	unmap_sysmem(buf);
> +
> +	ut_asserteq(2, env_get_hex("memmatches", 0));
> +	ut_asserteq(0x63, env_get_hex("memaddr", 0));
> +	ut_asserteq(0x63, env_get_hex("mempos", 0));
> +
> +	return 0;
> +}
> +DM_TEST(dm_test_ms_mult, 0);
> +
> +/* Test 'ms' command with string */
> +static int dm_test_ms_s(struct unit_test_state *uts)
> +{
> +	static const char str[] = "hello";
> +	static const char str2[] = "hellothere";
> +	char *buf;
> +
> +	buf = map_sysmem(0, BUF_SIZE);
> +	memset(buf, '\0', BUF_SIZE);
> +	strcpy(buf + 0x1e, str);
> +	strcpy(buf + 0x63, str);
> +	strcpy(buf + 0xa1, str2);
> +	console_record_reset();
> +	run_command("ms.s 0 100 hello", 0);
> +	ut_assert_nextline("00000010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 68 65    ..............he");
> +	ut_assert_nextline("00000020: 6c 6c 6f 00 00 00 00 00 00 00 00 00 00 00 00 00    llo.............");
> +	ut_assert_nextline("--");
> +	ut_assert_nextline("00000060: 00 00 00 68 65 6c 6c 6f 00 00 00 00 00 00 00 00    ...hello........");
> +	ut_assert_nextline("--");
> +	ut_assert_nextline("000000a0: 00 68 65 6c 6c 6f 74 68 65 72 65 00 00 00 00 00    .hellothere.....");
> +	ut_assert_nextline("3 matches");
> +	ut_assert_console_end();
> +
> +	ut_asserteq(3, env_get_hex("memmatches", 0));
> +	ut_asserteq(0xa1, env_get_hex("memaddr", 0));
> +	ut_asserteq(0xa1, env_get_hex("mempos", 0));
> +
> +	console_record_reset();
> +	run_command("ms.s 0 100 hello there", 0);
> +	ut_assert_nextline("000000a0: 00 68 65 6c 6c 6f 74 68 65 72 65 00 00 00 00 00    .hellothere.....");
> +	ut_assert_nextline("1 match");
> +	ut_assert_console_end();
> +
> +	ut_asserteq(1, env_get_hex("memmatches", 0));
> +	ut_asserteq(0xa1, env_get_hex("memaddr", 0));
> +	ut_asserteq(0xa1, env_get_hex("mempos", 0));
> +
> +	unmap_sysmem(buf);
> +
> +	return 0;
> +}
> +DM_TEST(dm_test_ms_s, 0);
> +
> +/* Test 'ms' command with limit */
> +static int dm_test_ms_limit(struct unit_test_state *uts)
> +{
> +	u8 *buf;
> +
> +	buf = map_sysmem(0, BUF_SIZE + 1);
> +	memset(buf, '\0', BUF_SIZE);
> +	buf[0x0] = 0x12;
> +	buf[0x31] = 0x12;
> +	buf[0x62] = 0x12;
> +	buf[0x76] = 0x12;
> +	console_record_reset();
> +	run_command("ms.b -l2 1 ff 12", 0);
> +	ut_assert_nextline("00000030: 00 12 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................");
> +	ut_assert_nextline("--");
> +	ut_assert_nextlinen("00000060: 00 00 12 00 00 00 00 00 00 00 00 00 00 00 00 00");
> +	ut_assert_nextline("2 matches (repeat command to check for more)");
> +	ut_assert_console_end();
> +
> +	ut_asserteq(2, env_get_hex("memmatches", 0));
> +	ut_asserteq(0x62, env_get_hex("memaddr", 0));
> +	ut_asserteq(0x61, env_get_hex("mempos", 0));
> +
> +	unmap_sysmem(buf);
> +
> +	return 0;
> +}
> +DM_TEST(dm_test_ms_limit, 0);
> +
> +/* Test 'ms' command in quiet mode */
> +static int dm_test_ms_quiet(struct unit_test_state *uts)
> +{
> +	u8 *buf;
> +
> +	buf = map_sysmem(0, BUF_SIZE + 1);
> +	memset(buf, '\0', BUF_SIZE);
> +	buf[0x0] = 0x12;
> +	buf[0x31] = 0x12;
> +	buf[0x62] = 0x12;
> +	buf[0x76] = 0x12;
> +	console_record_reset();
> +	run_command("ms.b -l2 1 ff 12", 0);
> +	ut_assert_console_end();
> +	unmap_sysmem(buf);
> +
> +	return 0;
> +}
> +DM_TEST(dm_test_ms_quiet, 0);
> +
> 


Viele Gr??e,
Stefan
Michal Simek June 3, 2020, 7:07 a.m. UTC | #2
On 03. 06. 20 3:26, Simon Glass wrote:
> It is useful to be able to find hex values and strings in a memory range.
> Add a command to support this.
> 
> cmd: Fix 'md' and add a memory-search command
> At present 'md.q' is broken. This series provides a fix for this. It also
> implements a new memory-search command called 'ms'. It allows searching
> memory for hex and string data.
> END

END likely shouldn't be here.
Recently I have met with the case that I have strings in i2c eeprom and
need to move them to variable. And I didn't find any way how to do it.
That's why I am curious if you are introducing this new command to also
in case of string search to fill any variable which will contain this
string.

Thanks,
Michal
Simon Glass June 4, 2020, 2:59 a.m. UTC | #3
Hi Michal,

On Wed, 3 Jun 2020 at 01:08, Michal Simek <michal.simek at xilinx.com> wrote:
>
> On 03. 06. 20 3:26, Simon Glass wrote:
> > It is useful to be able to find hex values and strings in a memory range.
> > Add a command to support this.
> >
> > cmd: Fix 'md' and add a memory-search command
> > At present 'md.q' is broken. This series provides a fix for this. It also
> > implements a new memory-search command called 'ms'. It allows searching
> > memory for hex and string data.
> > END
>
> END likely shouldn't be here.

Oops

> Recently I have met with the case that I have strings in i2c eeprom and
> need to move them to variable. And I didn't find any way how to do it.
> That's why I am curious if you are introducing this new command to also
> in case of string search to fill any variable which will contain this
> string.

Sorry it is just for memory-mapped things at present. But like we have
'i2c md' I suppose we could have 'i2c ms'.

Regards,
Simon
Michal Simek June 4, 2020, 8:33 a.m. UTC | #4
On 04. 06. 20 4:59, Simon Glass wrote:
> Hi Michal,
> 
> On Wed, 3 Jun 2020 at 01:08, Michal Simek <michal.simek at xilinx.com> wrote:
>>
>> On 03. 06. 20 3:26, Simon Glass wrote:
>>> It is useful to be able to find hex values and strings in a memory range.
>>> Add a command to support this.
>>>
>>> cmd: Fix 'md' and add a memory-search command
>>> At present 'md.q' is broken. This series provides a fix for this. It also
>>> implements a new memory-search command called 'ms'. It allows searching
>>> memory for hex and string data.
>>> END
>>
>> END likely shouldn't be here.
> 
> Oops
> 
>> Recently I have met with the case that I have strings in i2c eeprom and
>> need to move them to variable. And I didn't find any way how to do it.
>> That's why I am curious if you are introducing this new command to also
>> in case of string search to fill any variable which will contain this
>> string.
> 
> Sorry it is just for memory-mapped things at present. But like we have
> 'i2c md' I suppose we could have 'i2c ms'.

It wouldn't matter. I can do i2c read to memory and then ms to do it.
But question remains. When you find the string in memory how you want to
work with it? You need to have a way to move it to variable and use it
as the part of your script.

Thanks,
Michal
Simon Glass June 4, 2020, 1 p.m. UTC | #5
Hi Michal,

On Thu, 4 Jun 2020 at 02:33, Michal Simek <michal.simek at xilinx.com> wrote:
>
> On 04. 06. 20 4:59, Simon Glass wrote:
> > Hi Michal,
> >
> > On Wed, 3 Jun 2020 at 01:08, Michal Simek <michal.simek at xilinx.com> wrote:
> >>
> >> On 03. 06. 20 3:26, Simon Glass wrote:
> >>> It is useful to be able to find hex values and strings in a memory range.
> >>> Add a command to support this.
> >>>
> >>> cmd: Fix 'md' and add a memory-search command
> >>> At present 'md.q' is broken. This series provides a fix for this. It also
> >>> implements a new memory-search command called 'ms'. It allows searching
> >>> memory for hex and string data.
> >>> END
> >>
> >> END likely shouldn't be here.
> >
> > Oops
> >
> >> Recently I have met with the case that I have strings in i2c eeprom and
> >> need to move them to variable. And I didn't find any way how to do it.
> >> That's why I am curious if you are introducing this new command to also
> >> in case of string search to fill any variable which will contain this
> >> string.
> >
> > Sorry it is just for memory-mapped things at present. But like we have
> > 'i2c md' I suppose we could have 'i2c ms'.
>
> It wouldn't matter. I can do i2c read to memory and then ms to do it.
> But question remains. When you find the string in memory how you want to
> work with it? You need to have a way to move it to variable and use it
> as the part of your script.

Ah OK I didn't think of that.

I suppose you could use $mempos to find it, if we had a way to move a
string from memory to an env var? Does that exist? If not, setexpr
could be enhanced to do it quite easily.

Regards,
Simon
Michal Simek June 4, 2020, 1:05 p.m. UTC | #6
On 04. 06. 20 15:00, Simon Glass wrote:
> Hi Michal,
> 
> On Thu, 4 Jun 2020 at 02:33, Michal Simek <michal.simek at xilinx.com> wrote:
>>
>> On 04. 06. 20 4:59, Simon Glass wrote:
>>> Hi Michal,
>>>
>>> On Wed, 3 Jun 2020 at 01:08, Michal Simek <michal.simek at xilinx.com> wrote:
>>>>
>>>> On 03. 06. 20 3:26, Simon Glass wrote:
>>>>> It is useful to be able to find hex values and strings in a memory range.
>>>>> Add a command to support this.
>>>>>
>>>>> cmd: Fix 'md' and add a memory-search command
>>>>> At present 'md.q' is broken. This series provides a fix for this. It also
>>>>> implements a new memory-search command called 'ms'. It allows searching
>>>>> memory for hex and string data.
>>>>> END
>>>>
>>>> END likely shouldn't be here.
>>>
>>> Oops
>>>
>>>> Recently I have met with the case that I have strings in i2c eeprom and
>>>> need to move them to variable. And I didn't find any way how to do it.
>>>> That's why I am curious if you are introducing this new command to also
>>>> in case of string search to fill any variable which will contain this
>>>> string.
>>>
>>> Sorry it is just for memory-mapped things at present. But like we have
>>> 'i2c md' I suppose we could have 'i2c ms'.
>>
>> It wouldn't matter. I can do i2c read to memory and then ms to do it.
>> But question remains. When you find the string in memory how you want to
>> work with it? You need to have a way to move it to variable and use it
>> as the part of your script.
> 
> Ah OK I didn't think of that.
> 
> I suppose you could use $mempos to find it, if we had a way to move a
> string from memory to an env var? Does that exist? If not, setexpr
> could be enhanced to do it quite easily.

You will find it that what's your ms does. But I haven't seen that
setexpr part of that.
Also when I find that string I should be able to for example write my
variable to that location.

What was the use case you had in your mind how you want to handle string
when you find it?

Thanks,
Michal
Simon Glass June 4, 2020, 1:09 p.m. UTC | #7
Hi Michal,

On Thu, 4 Jun 2020 at 07:05, Michal Simek <michal.simek at xilinx.com> wrote:
>
> On 04. 06. 20 15:00, Simon Glass wrote:
> > Hi Michal,
> >
> > On Thu, 4 Jun 2020 at 02:33, Michal Simek <michal.simek at xilinx.com> wrote:
> >>
> >> On 04. 06. 20 4:59, Simon Glass wrote:
> >>> Hi Michal,
> >>>
> >>> On Wed, 3 Jun 2020 at 01:08, Michal Simek <michal.simek at xilinx.com> wrote:
> >>>>
> >>>> On 03. 06. 20 3:26, Simon Glass wrote:
> >>>>> It is useful to be able to find hex values and strings in a memory range.
> >>>>> Add a command to support this.
> >>>>>
> >>>>> cmd: Fix 'md' and add a memory-search command
> >>>>> At present 'md.q' is broken. This series provides a fix for this. It also
> >>>>> implements a new memory-search command called 'ms'. It allows searching
> >>>>> memory for hex and string data.
> >>>>> END
> >>>>
> >>>> END likely shouldn't be here.
> >>>
> >>> Oops
> >>>
> >>>> Recently I have met with the case that I have strings in i2c eeprom and
> >>>> need to move them to variable. And I didn't find any way how to do it.
> >>>> That's why I am curious if you are introducing this new command to also
> >>>> in case of string search to fill any variable which will contain this
> >>>> string.
> >>>
> >>> Sorry it is just for memory-mapped things at present. But like we have
> >>> 'i2c md' I suppose we could have 'i2c ms'.
> >>
> >> It wouldn't matter. I can do i2c read to memory and then ms to do it.
> >> But question remains. When you find the string in memory how you want to
> >> work with it? You need to have a way to move it to variable and use it
> >> as the part of your script.
> >
> > Ah OK I didn't think of that.
> >
> > I suppose you could use $mempos to find it, if we had a way to move a
> > string from memory to an env var? Does that exist? If not, setexpr
> > could be enhanced to do it quite easily.
>
> You will find it that what's your ms does. But I haven't seen that
> setexpr part of that.
> Also when I find that string I should be able to for example write my
> variable to that location.
>
> What was the use case you had in your mind how you want to handle string
> when you find it?

My use case is just to interactively search memory for things - e.g.
ACPI tables, pointers to addresses and the like. Useful for debugging.

Regards,
Simon
Michal Simek June 4, 2020, 1:10 p.m. UTC | #8
On 04. 06. 20 15:09, Simon Glass wrote:
> Hi Michal,
> 
> On Thu, 4 Jun 2020 at 07:05, Michal Simek <michal.simek at xilinx.com> wrote:
>>
>> On 04. 06. 20 15:00, Simon Glass wrote:
>>> Hi Michal,
>>>
>>> On Thu, 4 Jun 2020 at 02:33, Michal Simek <michal.simek at xilinx.com> wrote:
>>>>
>>>> On 04. 06. 20 4:59, Simon Glass wrote:
>>>>> Hi Michal,
>>>>>
>>>>> On Wed, 3 Jun 2020 at 01:08, Michal Simek <michal.simek at xilinx.com> wrote:
>>>>>>
>>>>>> On 03. 06. 20 3:26, Simon Glass wrote:
>>>>>>> It is useful to be able to find hex values and strings in a memory range.
>>>>>>> Add a command to support this.
>>>>>>>
>>>>>>> cmd: Fix 'md' and add a memory-search command
>>>>>>> At present 'md.q' is broken. This series provides a fix for this. It also
>>>>>>> implements a new memory-search command called 'ms'. It allows searching
>>>>>>> memory for hex and string data.
>>>>>>> END
>>>>>>
>>>>>> END likely shouldn't be here.
>>>>>
>>>>> Oops
>>>>>
>>>>>> Recently I have met with the case that I have strings in i2c eeprom and
>>>>>> need to move them to variable. And I didn't find any way how to do it.
>>>>>> That's why I am curious if you are introducing this new command to also
>>>>>> in case of string search to fill any variable which will contain this
>>>>>> string.
>>>>>
>>>>> Sorry it is just for memory-mapped things at present. But like we have
>>>>> 'i2c md' I suppose we could have 'i2c ms'.
>>>>
>>>> It wouldn't matter. I can do i2c read to memory and then ms to do it.
>>>> But question remains. When you find the string in memory how you want to
>>>> work with it? You need to have a way to move it to variable and use it
>>>> as the part of your script.
>>>
>>> Ah OK I didn't think of that.
>>>
>>> I suppose you could use $mempos to find it, if we had a way to move a
>>> string from memory to an env var? Does that exist? If not, setexpr
>>> could be enhanced to do it quite easily.
>>
>> You will find it that what's your ms does. But I haven't seen that
>> setexpr part of that.
>> Also when I find that string I should be able to for example write my
>> variable to that location.
>>
>> What was the use case you had in your mind how you want to handle string
>> when you find it?
> 
> My use case is just to interactively search memory for things - e.g.
> ACPI tables, pointers to addresses and the like. Useful for debugging.

ok. Got it.

M
Simon Glass June 8, 2020, 2:43 a.m. UTC | #9
Hi Stefan,

On Wed, 3 Jun 2020 at 00:05, Stefan Roese <sr at denx.de> wrote:
>
> On 03.06.20 03:26, Simon Glass wrote:
> > It is useful to be able to find hex values and strings in a memory range.
> > Add a command to support this.
> >
> > cmd: Fix 'md' and add a memory-search command
> > At present 'md.q' is broken. This series provides a fix for this.
>
> Perhaps this commit text belongs to a patchset cover letter? It does
> not really match this explicit patch.
>
> BTW: Why is md.q broken? I'm using it on my Octeon 64bit platform
> without any issues so far.

For me the 'q' size of md is not handled correctly in print_buffer(),
because of the missing compiler.h header file.

I'm going to send a patch for just that, separate from this series.

>
Regards,
Simon
diff mbox series

Patch

diff --git a/README b/README
index 17dc0ee33b..8abcff0783 100644
--- a/README
+++ b/README
@@ -3283,6 +3283,7 @@  md	- memory display
 mm	- memory modify (auto-incrementing)
 nm	- memory modify (constant address)
 mw	- memory write (fill)
+ms	- memory search
 cp	- memory copy
 cmp	- memory compare
 crc32	- checksum calculation
@@ -3528,6 +3529,15 @@  List of environment variables (most likely not complete):
 		  CONFIG_NET_RETRY_COUNT, if defined. This value has
 		  precedence over the valu based on CONFIG_NET_RETRY_COUNT.
 
+  memmatches	- Number of matches found by the last 'ms' command, in hex
+
+  memaddr	- Address of the last match found by the 'ms' command, in hex,
+		  or 0 if none
+
+  mempos	- Index position of the last match found by the 'ms' command,
+		  in units of the size (.b, .w, .l) of the search
+
+
 The following image location variables contain the location of images
 used in booting. The "Image" column gives the role of the image and is
 not an environment variable name. The other columns are environment
diff --git a/cmd/Kconfig b/cmd/Kconfig
index 153864c587..a02a376d49 100644
--- a/cmd/Kconfig
+++ b/cmd/Kconfig
@@ -717,6 +717,20 @@  config CMD_MEMORY
 	    base - print or set address offset
 	    loop - initialize loop on address range
 
+config MEM_SEARCH
+	bool "ms - Memory search"
+	help
+	  Memory-search command
+
+	  This allows searching through a region of memory looking for hex
+	  data (byte, 16-bit word, 32-bit long, also 64-bit on machines that
+	  support it). It is also possible to search for a string. The
+	  command accepts a memory range and a list of values to search for.
+	  The values need to appear in memory in the same order they are given
+	  in the command. At most 10 matches can be returned at a time, but
+	  pressing return will show the next 10 matches. Environment variables
+	  are set for use with scripting (memmatches, memaddr, mempos).
+
 config CMD_MX_CYCLIC
 	bool "Enable cyclic md/mw commands"
 	depends on CMD_MEMORY
diff --git a/cmd/mem.c b/cmd/mem.c
index 9ab6b1dd08..575893c18d 100644
--- a/cmd/mem.c
+++ b/cmd/mem.c
@@ -25,6 +25,7 @@ 
 #include <asm/io.h>
 #include <linux/bitops.h>
 #include <linux/compiler.h>
+#include <linux/ctype.h>
 #include <linux/delay.h>
 
 DECLARE_GLOBAL_DATA_PTR;
@@ -52,6 +53,10 @@  static ulong	dp_last_length = 0x40;
 static ulong	mm_last_addr, mm_last_size;
 
 static	ulong	base_address = 0;
+#ifdef CONFIG_MEM_SEARCH
+static u8 search_buf[64];
+static uint search_len;
+#endif
 
 /* Memory Display
  *
@@ -362,6 +367,142 @@  static int do_mem_cp(struct cmd_tbl *cmdtp, int flag, int argc,
 	return 0;
 }
 
+#ifdef CONFIG_MEM_SEARCH
+static int do_mem_search(struct cmd_tbl *cmdtp, int flag, int argc,
+			 char *const argv[])
+{
+	ulong addr, length, bytes, offset;
+	u8 *ptr, *end, *buf;
+	bool quiet = false;
+	ulong last_pos;		/* Offset of last match in 'size' units*/
+	ulong last_addr;	/* Address of last displayed line */
+	int limit = 10;
+	int count;
+	int size;
+	int i;
+
+	/* We use the last specified parameters, unless new ones are entered */
+	addr = dp_last_addr;
+	size = dp_last_size;
+	length = dp_last_length;
+
+	if (argc < 3)
+		return CMD_RET_USAGE;
+
+	if ((!flag & CMD_FLAG_REPEAT)) {
+		/*
+		 * Check for a size specification.
+		 * Defaults to long if no or incorrect specification.
+		 */
+		size = cmd_get_data_size(argv[0], 4);
+		if (size < 0 && size != -2 /* string */)
+			return 1;
+
+		argc--; argv++;
+		while (argc && *argv[0] == '-') {
+			int ch = argv[0][1];
+
+			if (ch == 'q')
+				quiet = true;
+			else if (ch == 'l' && isxdigit(argv[0][2]))
+				limit = simple_strtoul(argv[0] + 2, NULL, 16);
+			else
+				return CMD_RET_USAGE;
+			argc--; argv++;
+		}
+
+		/* Address is specified since argc > 1 */
+		addr = simple_strtoul(argv[0], NULL, 16);
+		addr += base_address;
+
+		/* Length is the number of objects, not number of bytes */
+		length = simple_strtoul(argv[1], NULL, 16);
+
+		/* Read the bytes to search for */
+		end = search_buf + sizeof(search_buf);
+		for (i = 2, ptr = search_buf; i < argc && ptr < end; i++) {
+			if (SUPPORT_64BIT_DATA && size == 8) {
+				u64 val = simple_strtoull(argv[i], NULL, 16);
+
+				*(u64 *)ptr = val;
+			} else if (size == -2) {  /* string */
+				int len = min(strlen(argv[i]),
+					      (size_t)(end - ptr));
+
+				memcpy(ptr, argv[i], len);
+				ptr += len;
+				continue;
+			} else {
+				u32 val = simple_strtoul(argv[i], NULL, 16);
+
+				switch (size) {
+				case 1:
+					*ptr = val;
+					break;
+				case 2:
+					*(u16 *)ptr = val;
+					break;
+				case 4:
+					*(u32 *)ptr = val;
+					break;
+				}
+			}
+			ptr += size;
+		}
+		search_len = ptr - search_buf;
+	}
+
+	/* Do the search */
+	if (size == -2)
+		size = 1;
+	bytes = size * length;
+	buf = map_sysmem(addr, bytes);
+	last_pos = 0;
+	last_addr = 0;
+	count = 0;
+	for (offset = 0; offset <= bytes - search_len && count < limit;
+	     offset += size) {
+		void *ptr = buf + offset;
+
+		if (!memcmp(ptr, search_buf, search_len)) {
+			uint align = (addr + offset) & 0xf;
+			ulong match = addr + offset;
+
+			if (!count || (last_addr & ~0xf) != (match & ~0xf)) {
+				if (!quiet) {
+					if (count)
+						printf("--\n");
+					print_buffer(match - align, ptr - align,
+						     size,
+						     ALIGN(search_len + align,
+							   16) / size, 0);
+				}
+				last_addr = match;
+				last_pos = offset / size;
+			}
+			count++;
+		}
+	}
+	if (!quiet) {
+		printf("%d match%s", count, count == 1 ? "" : "es");
+		if (count == limit)
+			printf(" (repeat command to check for more)");
+		printf("\n");
+	}
+	env_set_hex("memmatches", count);
+	env_set_hex("memaddr", last_addr);
+	env_set_hex("mempos", last_pos);
+
+	unmap_sysmem(buf);
+
+	dp_last_addr = addr + offset / size;
+	dp_last_size = size;
+	dp_last_length = length - offset / size;
+
+	return count ? 0 : CMD_RET_FAILURE;
+}
+#endif
+
 static int do_mem_base(struct cmd_tbl *cmdtp, int flag, int argc,
 		       char *const argv[])
 {
@@ -1196,6 +1337,16 @@  U_BOOT_CMD(
 	"[.b, .w, .l" HELP_Q "] addr1 addr2 count"
 );
 
+#ifdef CONFIG_MEM_SEARCH
+/**************************************************/
+U_BOOT_CMD(
+	ms,	255,	1,	do_mem_search,
+	"memory search",
+	"[.b, .w, .l" HELP_Q ", .s] [-q | -<n>] address #-of-objects <value>..."
+	"  -q = quiet, -l<val> = match limit" :
+);
+#endif
+
 #ifdef CONFIG_CMD_CRC32
 
 #ifndef CONFIG_CRC32_VERIFY
diff --git a/test/Makefile b/test/Makefile
index bab8f1a5c2..7c4039964e 100644
--- a/test/Makefile
+++ b/test/Makefile
@@ -3,6 +3,7 @@ 
 # (C) Copyright 2012 The Chromium Authors
 
 obj-$(CONFIG_SANDBOX) += bloblist.o
+obj-$(CONFIG_CMDLINE) += cmd/
 obj-$(CONFIG_UNIT_TEST) += cmd_ut.o
 obj-$(CONFIG_UNIT_TEST) += ut.o
 obj-$(CONFIG_SANDBOX) += command_ut.o
diff --git a/test/cmd/Makefile b/test/cmd/Makefile
new file mode 100644
index 0000000000..85d38f09e8
--- /dev/null
+++ b/test/cmd/Makefile
@@ -0,0 +1,5 @@ 
+# SPDX-License-Identifier: GPL-2.0+
+#
+# Copyright (c) 2013 Google, Inc
+
+obj-$(CONFIG_MEM_SEARCH) += mem_search.o
diff --git a/test/cmd/mem_search.c b/test/cmd/mem_search.c
new file mode 100644
index 0000000000..d57bfad398
--- /dev/null
+++ b/test/cmd/mem_search.c
@@ -0,0 +1,275 @@ 
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Tests for memory commands
+ *
+ * Copyright 2020 Google LLC
+ * Written by Simon Glass <sjg at chromium.org>
+ */
+
+#include <common.h>
+#include <console.h>
+#include <mapmem.h>
+#include <dm/test.h>
+#include <test/ut.h>
+
+#define BUF_SIZE	0x100
+
+/* Test 'ms' command with bytes */
+static int dm_test_ms_b(struct unit_test_state *uts)
+{
+	u8 *buf;
+
+	buf = map_sysmem(0, BUF_SIZE + 1);
+	memset(buf, '\0', BUF_SIZE);
+	buf[0x0] = 0x12;
+	buf[0x31] = 0x12;
+	buf[0xff] = 0x12;
+	buf[0x100] = 0x12;
+	console_record_reset();
+	run_command("ms.b 1 ff 12", 0);
+	ut_assert_nextline("00000030: 00 12 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................");
+	ut_assert_nextline("--");
+	ut_assert_nextline("000000f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 12    ................");
+	ut_assert_nextline("2 matches");
+	ut_assert_console_end();
+
+	ut_asserteq(2, env_get_hex("memmatches", 0));
+	ut_asserteq(0xff, env_get_hex("memaddr", 0));
+	ut_asserteq(0xfe, env_get_hex("mempos", 0));
+
+	unmap_sysmem(buf);
+
+	return 0;
+}
+DM_TEST(dm_test_ms_b, 0);
+
+/* Test 'ms' command with 16-bit values */
+static int dm_test_ms_w(struct unit_test_state *uts)
+{
+	u16 *buf;
+
+	buf = map_sysmem(0, BUF_SIZE + 2);
+	memset(buf, '\0', BUF_SIZE);
+	buf[0x34 / 2] = 0x1234;
+	buf[BUF_SIZE / 2] = 0x1234;
+	console_record_reset();
+	run_command("ms.w 0 80 1234", 0);
+	ut_assert_nextline("00000030: 0000 0000 1234 0000 0000 0000 0000 0000    ....4...........");
+	ut_assert_nextline("1 match");
+	ut_assert_console_end();
+
+	ut_asserteq(1, env_get_hex("memmatches", 0));
+	ut_asserteq(0x34, env_get_hex("memaddr", 0));
+	ut_asserteq(0x34 / 2, env_get_hex("mempos", 0));
+
+	unmap_sysmem(buf);
+
+	return 0;
+}
+DM_TEST(dm_test_ms_w, 0);
+
+/* Test 'ms' command with 32-bit values */
+static int dm_test_ms_l(struct unit_test_state *uts)
+{
+	u32 *buf;
+
+	buf = map_sysmem(0, BUF_SIZE + 4);
+	memset(buf, '\0', BUF_SIZE);
+	buf[0x38 / 4] = 0x12345678;
+	buf[BUF_SIZE / 4] = 0x12345678;
+	console_record_reset();
+	run_command("ms 0 40 12345678", 0);
+	ut_assert_nextline("00000030: 00000000 00000000 12345678 00000000    ........xV4.....");
+	ut_assert_nextline("1 match");
+	ut_assert_console_end();
+
+	ut_asserteq(1, env_get_hex("memmatches", 0));
+	ut_asserteq(0x38, env_get_hex("memaddr", 0));
+	ut_asserteq(0x38 / 4, env_get_hex("mempos", 0));
+
+	console_record_reset();
+	run_command("ms 0 80 12345679", 0);
+	ut_assert_nextline("0 matches");
+	ut_assert_console_end();
+
+	ut_asserteq(0, env_get_hex("memmatches", 0));
+	ut_asserteq(0, env_get_hex("memaddr", 0));
+	ut_asserteq(0 / 4, env_get_hex("mempos", 0));
+
+	unmap_sysmem(buf);
+
+	return 0;
+}
+DM_TEST(dm_test_ms_l, 0);
+
+/* Test 'ms' command with continuation */
+static int dm_test_ms_cont(struct unit_test_state *uts)
+{
+	char *const args[] = {"ms.b", "0", "100", "34"};
+	int repeatable;
+	u8 *buf;
+	int i;
+
+	buf = map_sysmem(0, BUF_SIZE);
+	memset(buf, '\0', BUF_SIZE);
+	for (i = 5; i < 0x33; i += 3)
+		buf[i] = 0x34;
+	console_record_reset();
+	run_command("ms.b 0 100 34", 0);
+	ut_assert_nextlinen("00000000: 00 00 00 00 00 34 00 00 34 00 00 34 00 00 34 00");
+	ut_assert_nextline("--");
+	ut_assert_nextlinen("00000010: 00 34 00 00 34 00 00 34 00 00 34 00 00 34 00 00");
+	ut_assert_nextline("--");
+	ut_assert_nextlinen("00000020: 34 00 00 34 00 00 34 00 00 34 00 00 34 00 00 34");
+	ut_assert_nextlinen("10 matches (repeat command to check for more)");
+	ut_assert_console_end();
+
+	ut_asserteq(10, env_get_hex("memmatches", 0));
+	ut_asserteq(0x20, env_get_hex("memaddr", 0));
+	ut_asserteq(0x20, env_get_hex("mempos", 0));
+
+	/*
+	 * run_command() ignoes the repeatable flag when using hush, so call
+	 * cmd_process() directly
+	 */
+	console_record_reset();
+	cmd_process(CMD_FLAG_REPEAT, 4, args, &repeatable, NULL);
+	ut_assert_nextlinen("00000020: 34 00 00 34 00 00 34 00 00 34 00 00 34 00 00 34");
+	ut_assert_nextline("--");
+	ut_assert_nextlinen("00000030: 00 00 34 00 00 00 00 00");
+	ut_assert_nextlinen("6 matches");
+	ut_assert_console_end();
+
+	ut_asserteq(6, env_get_hex("memmatches", 0));
+	ut_asserteq(0x32, env_get_hex("memaddr", 0));
+
+	/* 0x32 less 0x21, where the second search started */
+	ut_asserteq(0x11, env_get_hex("mempos", 0));
+
+	unmap_sysmem(buf);
+
+	return 0;
+}
+DM_TEST(dm_test_ms_cont, 0);
+
+/* Test 'ms' command with multiple values */
+static int dm_test_ms_mult(struct unit_test_state *uts)
+{
+	static const char str[] = "hello";
+	char *buf;
+
+	buf = map_sysmem(0, BUF_SIZE + 5);
+	memset(buf, '\0', BUF_SIZE);
+	strcpy(buf + 0x1e, str);
+	strcpy(buf + 0x63, str);
+	strcpy(buf + BUF_SIZE - strlen(str) + 1, str);
+	console_record_reset();
+	run_command("ms.b 0 100 68 65 6c 6c 6f", 0);
+	ut_assert_nextline("00000010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 68 65    ..............he");
+	ut_assert_nextline("00000020: 6c 6c 6f 00 00 00 00 00 00 00 00 00 00 00 00 00    llo.............");
+	ut_assert_nextline("--");
+	ut_assert_nextline("00000060: 00 00 00 68 65 6c 6c 6f 00 00 00 00 00 00 00 00    ...hello........");
+	ut_assert_nextline("2 matches");
+	ut_assert_console_end();
+	unmap_sysmem(buf);
+
+	ut_asserteq(2, env_get_hex("memmatches", 0));
+	ut_asserteq(0x63, env_get_hex("memaddr", 0));
+	ut_asserteq(0x63, env_get_hex("mempos", 0));
+
+	return 0;
+}
+DM_TEST(dm_test_ms_mult, 0);
+
+/* Test 'ms' command with string */
+static int dm_test_ms_s(struct unit_test_state *uts)
+{
+	static const char str[] = "hello";
+	static const char str2[] = "hellothere";
+	char *buf;
+
+	buf = map_sysmem(0, BUF_SIZE);
+	memset(buf, '\0', BUF_SIZE);
+	strcpy(buf + 0x1e, str);
+	strcpy(buf + 0x63, str);
+	strcpy(buf + 0xa1, str2);
+	console_record_reset();
+	run_command("ms.s 0 100 hello", 0);
+	ut_assert_nextline("00000010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 68 65    ..............he");
+	ut_assert_nextline("00000020: 6c 6c 6f 00 00 00 00 00 00 00 00 00 00 00 00 00    llo.............");
+	ut_assert_nextline("--");
+	ut_assert_nextline("00000060: 00 00 00 68 65 6c 6c 6f 00 00 00 00 00 00 00 00    ...hello........");
+	ut_assert_nextline("--");
+	ut_assert_nextline("000000a0: 00 68 65 6c 6c 6f 74 68 65 72 65 00 00 00 00 00    .hellothere.....");
+	ut_assert_nextline("3 matches");
+	ut_assert_console_end();
+
+	ut_asserteq(3, env_get_hex("memmatches", 0));
+	ut_asserteq(0xa1, env_get_hex("memaddr", 0));
+	ut_asserteq(0xa1, env_get_hex("mempos", 0));
+
+	console_record_reset();
+	run_command("ms.s 0 100 hello there", 0);
+	ut_assert_nextline("000000a0: 00 68 65 6c 6c 6f 74 68 65 72 65 00 00 00 00 00    .hellothere.....");
+	ut_assert_nextline("1 match");
+	ut_assert_console_end();
+
+	ut_asserteq(1, env_get_hex("memmatches", 0));
+	ut_asserteq(0xa1, env_get_hex("memaddr", 0));
+	ut_asserteq(0xa1, env_get_hex("mempos", 0));
+
+	unmap_sysmem(buf);
+
+	return 0;
+}
+DM_TEST(dm_test_ms_s, 0);
+
+/* Test 'ms' command with limit */
+static int dm_test_ms_limit(struct unit_test_state *uts)
+{
+	u8 *buf;
+
+	buf = map_sysmem(0, BUF_SIZE + 1);
+	memset(buf, '\0', BUF_SIZE);
+	buf[0x0] = 0x12;
+	buf[0x31] = 0x12;
+	buf[0x62] = 0x12;
+	buf[0x76] = 0x12;
+	console_record_reset();
+	run_command("ms.b -l2 1 ff 12", 0);
+	ut_assert_nextline("00000030: 00 12 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................");
+	ut_assert_nextline("--");
+	ut_assert_nextlinen("00000060: 00 00 12 00 00 00 00 00 00 00 00 00 00 00 00 00");
+	ut_assert_nextline("2 matches (repeat command to check for more)");
+	ut_assert_console_end();
+
+	ut_asserteq(2, env_get_hex("memmatches", 0));
+	ut_asserteq(0x62, env_get_hex("memaddr", 0));
+	ut_asserteq(0x61, env_get_hex("mempos", 0));
+
+	unmap_sysmem(buf);
+
+	return 0;
+}
+DM_TEST(dm_test_ms_limit, 0);
+
+/* Test 'ms' command in quiet mode */
+static int dm_test_ms_quiet(struct unit_test_state *uts)
+{
+	u8 *buf;
+
+	buf = map_sysmem(0, BUF_SIZE + 1);
+	memset(buf, '\0', BUF_SIZE);
+	buf[0x0] = 0x12;
+	buf[0x31] = 0x12;
+	buf[0x62] = 0x12;
+	buf[0x76] = 0x12;
+	console_record_reset();
+	run_command("ms.b -l2 1 ff 12", 0);
+	ut_assert_console_end();
+	unmap_sysmem(buf);
+
+	return 0;
+}
+DM_TEST(dm_test_ms_quiet, 0);
+