diff options
author | Luis Machado <luis.machado@linaro.org> | 2020-06-15 14:24:53 -0300 |
---|---|---|
committer | Luis Machado <luis.machado@linaro.org> | 2020-06-25 13:23:38 -0300 |
commit | 0c7c65e12f384d7035bf79e37ccbbc5d11cdc447 (patch) | |
tree | a9536df8f405757f841709c820d27977fec4e7ee | |
parent | e5f4e60d4955ba94516ef91d86f0f429a2c12612 (diff) | |
download | gdb-0c7c65e12f384d7035bf79e37ccbbc5d11cdc447.zip gdb-0c7c65e12f384d7035bf79e37ccbbc5d11cdc447.tar.gz gdb-0c7c65e12f384d7035bf79e37ccbbc5d11cdc447.tar.bz2 |
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:
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.
-rw-r--r-- | gdb/linux-tdep.c | 356 | ||||
-rw-r--r-- | gdb/linux-tdep.h | 4 |
2 files changed, 240 insertions, 120 deletions
diff --git a/gdb/linux-tdep.c b/gdb/linux-tdep.c index fd4337f..daab176 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. */ @@ -472,6 +497,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; } } @@ -1172,6 +1199,185 @@ typedef int linux_find_memory_region_ftype (ULONGEST vaddr, ULONGEST size, const char *filename, void *data); +/* 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 int +parse_smaps_data (const char *data, + std::vector<struct smaps_data> &smaps, + const char *mapsfilename) +{ + char *line, *t; + + gdb_assert (data != nullptr); + + smaps.clear (); + + line = strtok_r ((char *) data, "\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 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'"), mapsfilename); + 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"), + mapsfilename); + 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; + std::string fname (filename); + + map.start_address = addr; + map.end_address = endaddr; + map.filename = fname; + 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 0; +} + +/* 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. */ + parse_smaps_data (data.get (), smaps, smaps_file.c_str ()); + + if (!smaps.empty ()) + { + for (struct 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 @@ -1179,8 +1385,7 @@ linux_find_memory_regions_full (struct gdbarch *gdbarch, linux_find_memory_region_ftype *func, void *obfd) { - char mapsfilename[100]; - char coredumpfilter_name[100]; + std::string coredumpfilter_name; pid_t pid; /* Default dump behavior of coredump_filter (0x33), according to Documentation/filesystems/proc.txt from the Linux kernel @@ -1198,10 +1403,9 @@ linux_find_memory_regions_full (struct gdbarch *gdbarch, if (use_coredump_filter) { - xsnprintf (coredumpfilter_name, sizeof (coredumpfilter_name), - "/proc/%d/coredump_filter", pid); + coredumpfilter_name = string_printf ("/proc/%d/coredump_filter", pid); gdb::unique_xmalloc_ptr<char> coredumpfilterdata - = target_fileio_read_stralloc (NULL, coredumpfilter_name); + = target_fileio_read_stralloc (NULL, coredumpfilter_name.c_str ()); if (coredumpfilterdata != NULL) { unsigned int flags; @@ -1211,124 +1415,37 @@ linux_find_memory_regions_full (struct gdbarch *gdbarch, } } - xsnprintf (mapsfilename, sizeof mapsfilename, "/proc/%d/smaps", pid); + std::string mapsfilename = string_printf ("/proc/%d/smaps", pid); gdb::unique_xmalloc_ptr<char> data - = target_fileio_read_stralloc (NULL, mapsfilename); + = target_fileio_read_stralloc (NULL, mapsfilename.c_str ()); if (data == NULL) { /* Older Linux kernels did not support /proc/PID/smaps. */ - xsnprintf (mapsfilename, sizeof mapsfilename, "/proc/%d/maps", pid); - data = target_fileio_read_stralloc (NULL, mapsfilename); + mapsfilename = string_printf ("/proc/%d/maps", pid); + data = target_fileio_read_stralloc (NULL, mapsfilename.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 (data == nullptr) + return 1; - if (sscanf (line, "%64s", keyword) != 1) - { - warning (_("Error parsing {s,}maps file '%s'"), mapsfilename); - break; - } + std::vector<struct smaps_data> smaps; - 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); + /* Parse the contents of smaps into a vector. */ + parse_smaps_data (data.get (), smaps, mapsfilename.c_str ()); - 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"), - mapsfilename); - 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 (!smaps.empty ()) + { + for (struct smaps_data map : smaps) + { + int should_dump_p = 0; - if (has_anonymous) - should_dump_p = dump_mapping_p (filterflags, &v, priv, - mapping_anon_p, mapping_file_p, - filename, addr, offset); + if (map.has_anonymous) + should_dump_p = 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. @@ -1338,16 +1455,15 @@ linux_find_memory_regions_full (struct gdbarch *gdbarch, /* 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); + 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 0; } - return 1; + return 0; } /* A structure for passing information through diff --git a/gdb/linux-tdep.h b/gdb/linux-tdep.h index 91c2873..32c9c03 100644 --- a/gdb/linux-tdep.h +++ b/gdb/linux-tdep.h @@ -41,6 +41,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 *, |