diff mbox series

[v2,10/10] cmd: Add a memory-search command

Message ID 20200607204328.v2.10.Iaf24bd607de3836b8cd474fd0ae88452396ccb62@changeid
State New
Headers show
Series cmd: Fix 'md' and add a memory-search command | expand

Commit Message

Simon Glass June 8, 2020, 2:43 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.

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

Changes in v2:
- Rename the Kconfig to CONFIG_CMD_MEM_SEARCH
- Use a separate var for the remaining search length
- Drop unnecessary ':' in the U_BOOT_CMD
- Enable the command for sandbox
- Split the argc/argv changes over two lines
- Add the missing cover-letter tags
- Correct a bug when auto-repeating right at the end (and add test)
- Move tests to a new suite of their own, instead of DM tests
- Add -q flag to the 'quiet' test to test operation when console is enabled

 README                    |  10 ++
 cmd/Kconfig               |  14 ++
 cmd/mem.c                 | 157 ++++++++++++++++++
 configs/sandbox_defconfig |   1 +
 include/test/suites.h     |   1 +
 test/Makefile             |   1 +
 test/cmd/Makefile         |   6 +
 test/cmd/mem.c            |  20 +++
 test/cmd/mem_search.c     | 325 ++++++++++++++++++++++++++++++++++++++
 test/cmd_ut.c             |   2 +
 10 files changed, 537 insertions(+)
 create mode 100644 test/cmd/Makefile
 create mode 100644 test/cmd/mem.c
 create mode 100644 test/cmd/mem_search.c
diff mbox series

Patch

diff --git a/README b/README
index bcf1983631..83d22a74d5 100644
--- a/README
+++ b/README
@@ -3260,6 +3260,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
@@ -3505,6 +3506,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 192b3b262f..a85871cd48 100644
--- a/cmd/Kconfig
+++ b/cmd/Kconfig
@@ -718,6 +718,20 @@  config CMD_MEMORY
 	    base - print or set address offset
 	    loop - initialize loop on address range
 
+config CMD_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 ab2c9df621..f5b9b3238f 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;
@@ -50,6 +51,11 @@  static ulong	dp_last_length = 0x40;
 static ulong	mm_last_addr, mm_last_size;
 
 static	ulong	base_address = 0;
+#ifdef CONFIG_CMD_MEM_SEARCH
+static ulong dp_last_ms_length;
+static u8 search_buf[64];
+static uint search_len;
+#endif
 
 /* Memory Display
  *
@@ -360,6 +366,147 @@  static int do_mem_cp(struct cmd_tbl *cmdtp, int flag, int argc,
 	return 0;
 }
 
+#ifdef CONFIG_CMD_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 used_len;
+	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_ms_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 (MEM_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 && 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);
+
+	used_len = offset / size;
+	dp_last_addr = addr + used_len;
+	dp_last_size = size;
+	dp_last_ms_length = length < used_len ? 0 : length - used_len;
+
+	return count ? 0 : CMD_RET_FAILURE;
+}
+#endif
+
 static int do_mem_base(struct cmd_tbl *cmdtp, int flag, int argc,
 		       char *const argv[])
 {
@@ -1194,6 +1341,16 @@  U_BOOT_CMD(
 	"[.b, .w, .l" HELP_Q "] addr1 addr2 count"
 );
 
+#ifdef CONFIG_CMD_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/configs/sandbox_defconfig b/configs/sandbox_defconfig
index 5b7569319b..e55ec1a49c 100644
--- a/configs/sandbox_defconfig
+++ b/configs/sandbox_defconfig
@@ -37,6 +37,7 @@  CONFIG_CMD_NVEDIT_EFI=y
 CONFIG_LOOPW=y
 CONFIG_CMD_MD5SUM=y
 CONFIG_CMD_MEMINFO=y
+CONFIG_CMD_MEM_SEARCH=y
 CONFIG_CMD_MX_CYCLIC=y
 CONFIG_CMD_MEMTEST=y
 CONFIG_SYS_MEMTEST_START=0x00100000
diff --git a/include/test/suites.h b/include/test/suites.h
index f120b3a82a..ab7b3bd9ca 100644
--- a/include/test/suites.h
+++ b/include/test/suites.h
@@ -34,6 +34,7 @@  int do_ut_dm(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]);
 int do_ut_env(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]);
 int do_ut_lib(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]);
 int do_ut_log(struct cmd_tbl *cmdtp, int flag, int argc, char * const argv[]);
+int do_ut_mem(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]);
 int do_ut_optee(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]);
 int do_ut_overlay(struct cmd_tbl *cmdtp, int flag, int argc,
 		  char *const argv[]);
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..859dcda239
--- /dev/null
+++ b/test/cmd/Makefile
@@ -0,0 +1,6 @@ 
+# SPDX-License-Identifier: GPL-2.0+
+#
+# Copyright (c) 2013 Google, Inc
+
+obj-y += mem.o
+obj-$(CONFIG_CMD_MEM_SEARCH) += mem_search.o
diff --git a/test/cmd/mem.c b/test/cmd/mem.c
new file mode 100644
index 0000000000..fa6770e8c0
--- /dev/null
+++ b/test/cmd/mem.c
@@ -0,0 +1,20 @@ 
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Executes tests for memory-related commands
+ *
+ * Copyright 2020 Google LLC
+ */
+
+#include <common.h>
+#include <command.h>
+#include <test/suites.h>
+#include <test/test.h>
+
+int do_ut_mem(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
+{
+	struct unit_test *tests = ll_entry_start(struct unit_test, mem_test);
+	const int n_ents = ll_entry_count(struct unit_test, mem_test);
+
+	return cmd_ut_category("cmd_mem", "cmd_mem_", tests, n_ents, argc,
+			       argv);
+}
diff --git a/test/cmd/mem_search.c b/test/cmd/mem_search.c
new file mode 100644
index 0000000000..94942793a4
--- /dev/null
+++ b/test/cmd/mem_search.c
@@ -0,0 +1,325 @@ 
+// 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
+
+/* Declare a new mem test */
+#define MEM_TEST(_name, _flags)	UNIT_TEST(_name, _flags, mem_test)
+
+/* Test 'ms' command with bytes */
+static int mem_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;
+	ut_assertok(console_record_reset_enable());
+	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;
+}
+MEM_TEST(mem_test_ms_b, UT_TESTF_CONSOLE_REC);
+
+/* Test 'ms' command with 16-bit values */
+static int mem_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;
+	ut_assertok(console_record_reset_enable());
+	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;
+}
+MEM_TEST(mem_test_ms_w, UT_TESTF_CONSOLE_REC);
+
+/* Test 'ms' command with 32-bit values */
+static int mem_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;
+	ut_assertok(console_record_reset_enable());
+	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));
+
+	ut_assertok(console_record_reset_enable());
+	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;
+}
+MEM_TEST(mem_test_ms_l, UT_TESTF_CONSOLE_REC);
+
+/* Test 'ms' command with continuation */
+static int mem_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;
+	ut_assertok(console_record_reset_enable());
+	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
+	 */
+	ut_assertok(console_record_reset_enable());
+	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;
+}
+MEM_TEST(mem_test_ms_cont, UT_TESTF_CONSOLE_REC);
+
+/* Test that an 'ms' command with continuation stops at the end of the range */
+static int mem_test_ms_cont_end(struct unit_test_state *uts)
+{
+	char *const args[] = {"ms.b", "1", "ff", "12"};
+	int repeatable;
+	u8 *buf;
+
+	buf = map_sysmem(0, BUF_SIZE);
+	memset(buf, '\0', BUF_SIZE);
+	buf[0x0] = 0x12;
+	buf[0x31] = 0x12;
+	buf[0xff] = 0x12;
+	buf[0x100] = 0x12;
+	ut_assertok(console_record_reset_enable());
+	run_command("ms.b 1 ff 12", 0);
+	ut_assert_nextlinen("00000030");
+	ut_assert_nextlinen("--");
+	ut_assert_nextlinen("000000f0");
+	ut_assert_nextlinen("2 matches");
+	ut_assert_console_end();
+
+	/*
+	 * run_command() ignoes the repeatable flag when using hush, so call
+	 * cmd_process() directly.
+	 *
+	 * This should produce no matches.
+	 */
+	ut_assertok(console_record_reset_enable());
+	cmd_process(CMD_FLAG_REPEAT, 4, args, &repeatable, NULL);
+	ut_assert_nextlinen("0 matches");
+	ut_assert_console_end();
+
+	/* One more time */
+	ut_assertok(console_record_reset_enable());
+	cmd_process(CMD_FLAG_REPEAT, 4, args, &repeatable, NULL);
+	ut_assert_nextlinen("0 matches");
+	ut_assert_console_end();
+
+	unmap_sysmem(buf);
+
+	return 0;
+}
+MEM_TEST(mem_test_ms_cont_end, UT_TESTF_CONSOLE_REC);
+
+/* Test 'ms' command with multiple values */
+static int mem_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);
+	ut_assertok(console_record_reset_enable());
+	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;
+}
+MEM_TEST(mem_test_ms_mult, UT_TESTF_CONSOLE_REC);
+
+/* Test 'ms' command with string */
+static int mem_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);
+	ut_assertok(console_record_reset_enable());
+	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));
+
+	ut_assertok(console_record_reset_enable());
+	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;
+}
+MEM_TEST(mem_test_ms_s, UT_TESTF_CONSOLE_REC);
+
+/* Test 'ms' command with limit */
+static int mem_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;
+	ut_assertok(console_record_reset_enable());
+	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;
+}
+MEM_TEST(mem_test_ms_limit, UT_TESTF_CONSOLE_REC);
+
+/* Test 'ms' command in quiet mode */
+static int mem_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;
+	ut_assertok(console_record_reset_enable());
+	run_command("ms.b -q -l2 1 ff 12", 0);
+	ut_assert_console_end();
+	unmap_sysmem(buf);
+
+	ut_asserteq(2, env_get_hex("memmatches", 0));
+	ut_asserteq(0x62, env_get_hex("memaddr", 0));
+	ut_asserteq(0x61, env_get_hex("mempos", 0));
+
+	return 0;
+}
+MEM_TEST(mem_test_ms_quiet, UT_TESTF_CONSOLE_REC);
diff --git a/test/cmd_ut.c b/test/cmd_ut.c
index 1963f3792c..8f0bc688a2 100644
--- a/test/cmd_ut.c
+++ b/test/cmd_ut.c
@@ -74,6 +74,7 @@  static struct cmd_tbl cmd_ut_sub[] = {
 #ifdef CONFIG_UT_LOG
 	U_BOOT_CMD_MKENT(log, CONFIG_SYS_MAXARGS, 1, do_ut_log, "", ""),
 #endif
+	U_BOOT_CMD_MKENT(mem, CONFIG_SYS_MAXARGS, 1, do_ut_mem, "", ""),
 #ifdef CONFIG_UT_TIME
 	U_BOOT_CMD_MKENT(time, CONFIG_SYS_MAXARGS, 1, do_ut_time, "", ""),
 #endif
@@ -145,6 +146,7 @@  static char ut_help_text[] =
 #ifdef CONFIG_UT_LOG
 	"ut log [test-name] - test logging functions\n"
 #endif
+	"ut mem [test-name] - test memory-related commands\n"
 #ifdef CONFIG_UT_OPTEE
 	"ut optee [test-name]\n"
 #endif