From 1e735120b95236b1f0fd8c383e64b02b64909d7c Mon Sep 17 00:00:00 2001 From: Luis Machado Date: Mon, 15 Jun 2020 14:24:53 -0300 Subject: Refactor parsing of /proc//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 * linux-tdep.c (struct smaps_vmflags) : 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/ChangeLog | 12 ++ gdb/linux-tdep.c | 357 ++++++++++++++++++++++++++++++++++++------------------- gdb/linux-tdep.h | 4 + 3 files changed, 251 insertions(+), 122 deletions(-) diff --git a/gdb/ChangeLog b/gdb/ChangeLog index da4bb26c21c..9d155b8f5e5 100644 --- a/gdb/ChangeLog +++ b/gdb/ChangeLog @@ -1,3 +1,15 @@ +2021-03-24 Luis Machado + + * linux-tdep.c (struct smaps_vmflags) : 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 * linux-tdep.c (linux_find_memory_regions_full): Use std::string diff --git a/gdb/linux-tdep.c b/gdb/linux-tdep.c index 17ed9cac8a7..38c5ecf5b59 100644 --- a/gdb/linux-tdep.c +++ b/gdb/linux-tdep.c @@ -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//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//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 +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 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 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 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 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 diff --git a/gdb/linux-tdep.h b/gdb/linux-tdep.h index 7db6eceeab1..28b60e46579 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 *, -- cgit v1.2.1