[PATCH v5 14/25] Refactor parsing of /proc//smaps

Luis Machado via Gdb-patches
2021-01-27T20:21:52Z

Updates on v4:

- Update return type of parse_smaps_data.
- Misc formatting fixes

--

The Linux kernel exposes the information about MTE-protected pages via the
proc filesystem, more specifically through the smaps file.

What we're looking for is a mapping with the 'mt' flag, which tells us that
mapping was created with a PROT_MTE flag and, thus, is capable of using memory
tagging.

We already parse that file for other purposes (core file
generation/filtering), so this patch refactors the code to make the parsing
of the smaps file reusable for memory tagging.

The function linux_address_in_memtag_page uses the refactored code to allow
querying for memory tag support in a particular address, and it gets used in the
next patch.

gdb/ChangeLog:

YYYY-MM-DD Luis Machado <luis.machado@linaro.org>

* linux-tdep.c (struct smaps_vmflags) <memory_tagging>: New flag
bit.
(struct smaps_data): New struct.
(decode_vmflags): Handle the 'mt' flag.
(parse_smaps_data): New function, refactored from
linux_find_memory_regions_full.
(linux_address_in_memtag_page): New function.
(linux_find_memory_regions_full): Refactor into parse_smaps_data.
* linux-tdep.h (linux_address_in_memtag_page): New prototype.
---
gdb/linux-tdep.c | 359 +++++++++++++++++++++++++++++++----------------
gdb/linux-tdep.h | 4 +
2 files changed, 241 insertions(+), 122 deletions(-)

diff --git a/gdb/linux-tdep.c b/gdb/linux-tdep.c
index 35e05e12a6..abbd43e22c 100644
--- a/gdb/linux-tdep.c
+++ b/gdb/linux-tdep.c
@@ -86,8 +86,33 @@ struct smaps_vmflags
/* Is this a MAP_SHARED mapping (VM_SHARED, "sh"). */

unsigned int shared_mapping : 1;
+
+ /* Memory map has memory tagging enabled. */
+
+ unsigned int memory_tagging : 1;
};

+/* Data structure that holds the information contained in the
+ /proc/<pid>/smaps file. */
+
+struct smaps_data
+{
+ ULONGEST start_address;
+ ULONGEST end_address;
+ std::string filename;
+ struct smaps_vmflags vmflags;
+ bool read;
+ bool write;
+ bool exec;
+ bool priv;
+ bool has_anonymous;
+ bool mapping_anon_p;
+ bool mapping_file_p;
+
+ ULONGEST inode;
+ ULONGEST offset;
+};
+
/* Whether to take the /proc/PID/coredump_filter into account when
generating a corefile. */

@@ -474,6 +499,8 @@ decode_vmflags (char *p, struct smaps_vmflags *v)
v->exclude_coredump = 1;
else if (strcmp (s, "sh") == 0)
v->shared_mapping = 1;
+ else if (strcmp (s, "mt") == 0)
+ v->memory_tagging = 1;
}
}

@@ -1269,6 +1296,181 @@ typedef int linux_dump_mapping_p_ftype (filter_flags filterflags,
ULONGEST addr,
ULONGEST offset);

+/* Helper function to parse the contents of /proc/<pid>/smaps into a data
+ structure, for easy access.
+
+ DATA is the contents of the smaps file. The parsed contents are stored
+ into the SMAPS vector. */
+
+static std::vector<struct smaps_data>
+parse_smaps_data (const char *data,
+ const std::string maps_filename)
+{
+ char *line, *t;
+
+ gdb_assert (data != nullptr);
+
+ line = strtok_r ((char *) data, "\n", &t);
+
+ std::vector<struct smaps_data> smaps;
+
+ while (line != NULL)
+ {
+ ULONGEST addr, endaddr, offset, inode;
+ const char *permissions, *device, *filename;
+ struct smaps_vmflags v;
+ size_t permissions_len, device_len;
+ int read, write, exec, priv;
+ int has_anonymous = 0;
+ int mapping_anon_p;
+ int mapping_file_p;
+
+ memset (&v, 0, sizeof (v));
+ read_mapping (line, &addr, &endaddr, &permissions, &permissions_len,
+ &offset, &device, &device_len, &inode, &filename);
+ mapping_anon_p = mapping_is_anonymous_p (filename);
+ /* If the mapping is not anonymous, then we can consider it
+ to be file-backed. These two states (anonymous or
+ file-backed) seem to be exclusive, but they can actually
+ coexist. For example, if a file-backed mapping has
+ "Anonymous:" pages (see more below), then the Linux
+ kernel will dump this mapping when the user specified
+ that she only wants anonymous mappings in the corefile
+ (*even* when she explicitly disabled the dumping of
+ file-backed mappings). */
+ mapping_file_p = !mapping_anon_p;
+
+ /* Decode permissions. */
+ read = (memchr (permissions, 'r', permissions_len) != 0);
+ write = (memchr (permissions, 'w', permissions_len) != 0);
+ exec = (memchr (permissions, 'x', permissions_len) != 0);
+ /* 'private' here actually means VM_MAYSHARE, and not
+ VM_SHARED. In order to know if a mapping is really
+ private or not, we must check the flag "sh" in the
+ VmFlags field. This is done by decode_vmflags. However,
+ if we are using a Linux kernel released before the commit
+ 834f82e2aa9a8ede94b17b656329f850c1471514 (3.10), we will
+ not have the VmFlags there. In this case, there is
+ really no way to know if we are dealing with VM_SHARED,
+ so we just assume that VM_MAYSHARE is enough. */
+ priv = memchr (permissions, 'p', permissions_len) != 0;
+
+ /* Try to detect if region should be dumped by parsing smaps
+ counters. */
+ for (line = strtok_r (NULL, "\n", &t);
+ line != NULL && line[0] >= 'A' && line[0] <= 'Z';
+ line = strtok_r (NULL, "\n", &t))
+ {
+ char keyword[64 + 1];
+
+ if (sscanf (line, "%64s", keyword) != 1)
+ {
+ warning (_("Error parsing {s,}maps file '%s'"),
+ maps_filename.c_str ());
+ break;
+ }
+
+ if (strcmp (keyword, "Anonymous:") == 0)
+ {
+ /* Older Linux kernels did not support the
+ "Anonymous:" counter. Check it here. */
+ has_anonymous = 1;
+ }
+ else if (strcmp (keyword, "VmFlags:") == 0)
+ decode_vmflags (line, &v);
+
+ if (strcmp (keyword, "AnonHugePages:") == 0
+ || strcmp (keyword, "Anonymous:") == 0)
+ {
+ unsigned long number;
+
+ if (sscanf (line, "%*s%lu", &number) != 1)
+ {
+ warning (_("Error parsing {s,}maps file '%s' number"),
+ maps_filename.c_str ());
+ break;
+ }
+ if (number > 0)
+ {
+ /* Even if we are dealing with a file-backed
+ mapping, if it contains anonymous pages we
+ consider it to be *also* an anonymous
+ mapping, because this is what the Linux
+ kernel does:
+
+ // Dump segments that have been written to.
+ if (vma->anon_vma && FILTER(ANON_PRIVATE))
+ goto whole;
+
+ Note that if the mapping is already marked as
+ file-backed (i.e., mapping_file_p is
+ non-zero), then this is a special case, and
+ this mapping will be dumped either when the
+ user wants to dump file-backed *or* anonymous
+ mappings. */
+ mapping_anon_p = 1;
+ }
+ }
+ }
+ /* Save the smaps entry to the vector. */
+ struct smaps_data map;
+
+ map.start_address = addr;
+ map.end_address = endaddr;
+ map.filename = filename;
+ map.vmflags = v;
+ map.read = read? true : false;
+ map.write = write? true : false;
+ map.exec = exec? true : false;
+ map.priv = priv? true : false;
+ map.has_anonymous = has_anonymous;
+ map.mapping_anon_p = mapping_anon_p? true : false;
+ map.mapping_file_p = mapping_file_p? true : false;
+ map.offset = offset;
+ map.inode = inode;
+
+ smaps.emplace_back (map);
+ }
+
+ return smaps;
+}
+
+/* See linux-tdep.h. */
+
+bool
+linux_address_in_memtag_page (CORE_ADDR address)
+{
+ if (current_inferior ()->fake_pid_p)
+ return false;
+
+ pid_t pid = current_inferior ()->pid;
+
+ std::string smaps_file = string_printf ("/proc/%d/smaps", pid);
+
+ gdb::unique_xmalloc_ptr<char> data
+ = target_fileio_read_stralloc (NULL, smaps_file.c_str ());
+
+ if (data == nullptr)
+ return false;
+
+ std::vector<struct smaps_data> smaps;
+
+ /* Parse the contents of smaps into a vector. */
+ smaps = parse_smaps_data (data.get (), smaps_file);
+
+ for (const smaps_data &map : smaps)
+ {
+ /* Is the address within [start_address, end_address) in a page
+ mapped with memory tagging? */
+ if (address >= map.start_address
+ && address < map.end_address
+ && map.vmflags.memory_tagging)
+ return true;
+ }
+
+ return false;
+}
+
/* List memory regions in the inferior for a corefile. */

static int
@@ -1319,137 +1521,50 @@ linux_find_memory_regions_full (struct gdbarch *gdbarch,
/* Older Linux kernels did not support /proc/PID/smaps. */
maps_filename = string_printf ("/proc/%d/maps", pid);
data = target_fileio_read_stralloc (NULL, maps_filename.c_str ());
- }
-
- if (data != NULL)
- {
- char *line, *t;
-
- line = strtok_r (data.get (), "\n", &t);
- while (line != NULL)
- {
- ULONGEST addr, endaddr, offset, inode;
- const char *permissions, *device, *filename;
- struct smaps_vmflags v;
- size_t permissions_len, device_len;
- int read, write, exec, priv;
- int has_anonymous = 0;
- int should_dump_p = 0;
- int mapping_anon_p;
- int mapping_file_p;
-
- memset (&v, 0, sizeof (v));
- read_mapping (line, &addr, &endaddr, &permissions, &permissions_len,
- &offset, &device, &device_len, &inode, &filename);
- mapping_anon_p = mapping_is_anonymous_p (filename);
- /* If the mapping is not anonymous, then we can consider it
- to be file-backed. These two states (anonymous or
- file-backed) seem to be exclusive, but they can actually
- coexist. For example, if a file-backed mapping has
- "Anonymous:" pages (see more below), then the Linux
- kernel will dump this mapping when the user specified
- that she only wants anonymous mappings in the corefile
- (*even* when she explicitly disabled the dumping of
- file-backed mappings). */
- mapping_file_p = !mapping_anon_p;
-
- /* Decode permissions. */
- read = (memchr (permissions, 'r', permissions_len) != 0);
- write = (memchr (permissions, 'w', permissions_len) != 0);
- exec = (memchr (permissions, 'x', permissions_len) != 0);
- /* 'private' here actually means VM_MAYSHARE, and not
- VM_SHARED. In order to know if a mapping is really
- private or not, we must check the flag "sh" in the
- VmFlags field. This is done by decode_vmflags. However,
- if we are using a Linux kernel released before the commit
- 834f82e2aa9a8ede94b17b656329f850c1471514 (3.10), we will
- not have the VmFlags there. In this case, there is
- really no way to know if we are dealing with VM_SHARED,
- so we just assume that VM_MAYSHARE is enough. */
- priv = memchr (permissions, 'p', permissions_len) != 0;
-
- /* Try to detect if region should be dumped by parsing smaps
- counters. */
- for (line = strtok_r (NULL, "\n", &t);
- line != NULL && line[0] >= 'A' && line[0] <= 'Z';
- line = strtok_r (NULL, "\n", &t))
- {
- char keyword[64 + 1];

- if (sscanf (line, "%64s", keyword) != 1)
- {
- warning (_("Error parsing {s,}maps file '%s'"),
- maps_filename.c_str ());
- break;
- }
+ if (data == nullptr)
+ return 1;
+ }

- if (strcmp (keyword, "Anonymous:") == 0)
- {
- /* Older Linux kernels did not support the
- "Anonymous:" counter. Check it here. */
- has_anonymous = 1;
- }
- else if (strcmp (keyword, "VmFlags:") == 0)
- decode_vmflags (line, &v);
+ std::vector<struct smaps_data> smaps;

- if (strcmp (keyword, "AnonHugePages:") == 0
- || strcmp (keyword, "Anonymous:") == 0)
- {
- unsigned long number;
-
- if (sscanf (line, "%*s%lu", &number) != 1)
- {
- warning (_("Error parsing {s,}maps file '%s' number"),
- maps_filename.c_str ());
- break;
- }
- if (number > 0)
- {
- /* Even if we are dealing with a file-backed
- mapping, if it contains anonymous pages we
- consider it to be *also* an anonymous
- mapping, because this is what the Linux
- kernel does:
-
- // Dump segments that have been written to.
- if (vma->anon_vma && FILTER(ANON_PRIVATE))
- goto whole;
-
- Note that if the mapping is already marked as
- file-backed (i.e., mapping_file_p is
- non-zero), then this is a special case, and
- this mapping will be dumped either when the
- user wants to dump file-backed *or* anonymous
- mappings. */
- mapping_anon_p = 1;
- }
- }
- }
+ /* Parse the contents of smaps into a vector. */
+ smaps = parse_smaps_data (data.get (), maps_filename.c_str ());

- if (has_anonymous)
- should_dump_p = should_dump_mapping_p (filterflags, &v, priv,
- mapping_anon_p,
- mapping_file_p,
- filename, addr, offset);
- else
- {
- /* Older Linux kernels did not support the "Anonymous:" counter.
- If it is missing, we can't be sure - dump all the pages. */
- should_dump_p = 1;
- }
+ for (const struct smaps_data map : smaps)
+ {
+ int should_dump_p = 0;

- /* Invoke the callback function to create the corefile segment. */
- if (should_dump_p)
- func (addr, endaddr - addr, offset, inode,
- read, write, exec, 1, /* MODIFIED is true because we
- want to dump the mapping. */
- filename, obfd);
+ if (map.has_anonymous)
+ {
+ should_dump_p
+ = should_dump_mapping_p (filterflags, &map.vmflags,
+ map.priv,
+ map.mapping_anon_p,
+ map.mapping_file_p,
+ map.filename.c_str (),
+ map.start_address,
+ map.offset);
+ }
+ else
+ {
+ /* Older Linux kernels did not support the "Anonymous:" counter.
+ If it is missing, we can't be sure - dump all the pages. */
+ should_dump_p = 1;
}

- return 0;
+ /* Invoke the callback function to create the corefile segment. */
+ if (should_dump_p)
+ {
+ func (map.start_address, map.end_address - map.start_address,
+ map.offset, map.inode, map.read, map.write, map.exec,
+ 1, /* MODIFIED is true because we want to dump
+ the mapping. */
+ map.filename.c_str (), obfd);
+ }
}

- return 1;
+ return 0;
}

/* A structure for passing information through
diff --git a/gdb/linux-tdep.h b/gdb/linux-tdep.h
index 7db6eceeab..d72635215d 100644
--- a/gdb/linux-tdep.h
+++ b/gdb/linux-tdep.h
@@ -43,6 +43,10 @@ DEF_ENUM_FLAGS_TYPE (enum linux_siginfo_extra_field_values,
struct type *linux_get_siginfo_type_with_fields (struct gdbarch *gdbarch,
linux_siginfo_extra_fields);

+ /* Return true if ADDRESS is within the boundaries of a page mapped with
+ memory tagging protection. */
+bool linux_address_in_memtag_page (CORE_ADDR address);
+
typedef char *(*linux_collect_thread_registers_ftype) (const struct regcache *,
ptid_t,
bfd *, char *, int *,
--
2.25.1