Refactor parsing of /proc/<pid>/smaps

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:

2021-03-24  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.
This commit is contained in:
Luis Machado 2020-06-15 14:24:53 -03:00
parent 93e447c605
commit 1e735120b9
3 changed files with 251 additions and 122 deletions

View file

@ -1,3 +1,15 @@
2021-03-24 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.
2021-03-24 Luis Machado <luis.machado@linaro.org>
* linux-tdep.c (linux_find_memory_regions_full): Use std::string

View file

@ -88,8 +88,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. */
@ -476,6 +501,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;
}
}
@ -1271,6 +1298,180 @@ 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;
/* Parse the contents of smaps into a vector. */
std::vector<struct smaps_data> 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
@ -1321,137 +1522,49 @@ 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 == nullptr)
return 1;
}
if (data != NULL)
/* Parse the contents of smaps into a vector. */
std::vector<struct smaps_data> smaps
= parse_smaps_data (data.get (), maps_filename.c_str ());
for (const struct smaps_data &map : smaps)
{
char *line, *t;
int should_dump_p = 0;
line = strtok_r (data.get (), "\n", &t);
while (line != NULL)
if (map.has_anonymous)
{
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 (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;
}
}
}
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;
}
/* 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);
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

View file

@ -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 *,