diff options
Diffstat (limited to 'gdb/linux-tdep.c')
-rw-r--r-- | gdb/linux-tdep.c | 656 |
1 files changed, 435 insertions, 221 deletions
diff --git a/gdb/linux-tdep.c b/gdb/linux-tdep.c index fe880b3..5c4bbf6 100644 --- a/gdb/linux-tdep.c +++ b/gdb/linux-tdep.c @@ -1,6 +1,6 @@ /* Target-dependent code for GNU/Linux, architecture independent. - Copyright (C) 2009-2024 Free Software Foundation, Inc. + Copyright (C) 2009-2025 Free Software Foundation, Inc. This file is part of GDB. @@ -43,9 +43,10 @@ #include "gcore-elf.h" #include "solib-svr4.h" #include "memtag.h" +#include "cli/cli-style.h" +#include "gdbsupport/unordered_map.h" #include <ctype.h> -#include <unordered_map> /* This enum represents the values that the user can choose when informing the Linux kernel about which memory mappings will be @@ -213,7 +214,7 @@ get_linux_gdbarch_data (struct gdbarch *gdbarch) /* Linux-specific cached data. This is used by GDB for caching purposes for each inferior. This helps reduce the overhead of - transfering data from a remote target to the local host. */ + transferring data from a remote target to the local host. */ struct linux_info { /* Cache of the inferior's vsyscall/vDSO mapping range. Only valid @@ -457,7 +458,7 @@ struct mapping { ULONGEST addr; ULONGEST endaddr; - std::string_view permissions; + std::string permissions; ULONGEST offset; std::string_view device; ULONGEST inode; @@ -484,7 +485,8 @@ read_mapping (const char *line) const char *permissions_start = p; while (*p && !isspace (*p)) p++; - mapping.permissions = {permissions_start, (size_t) (p - permissions_start)}; + mapping.permissions = std::string (permissions_start, + (size_t) (p - permissions_start)); mapping.offset = strtoulst (p, &p, 16); @@ -628,9 +630,9 @@ mapping_is_anonymous_p (const char *filename) return 0; } -/* Return 0 if the memory mapping (which is related to FILTERFLAGS, V, - MAYBE_PRIVATE_P, MAPPING_ANONYMOUS_P, ADDR and OFFSET) should not - be dumped, or greater than 0 if it should. +/* Return false if the memory mapping represented by MAP should not be + dumped, or true if it should. FILTERFLAGS guides which mappings + should be dumped. In a nutshell, this is the logic that we follow in order to decide if a mapping should be dumped or not. @@ -675,11 +677,14 @@ mapping_is_anonymous_p (const char *filename) header (of a DSO or an executable, for example). If it is, and if the user is interested in dump it, then we should dump it. */ -static int -dump_mapping_p (filter_flags filterflags, const struct smaps_vmflags *v, - int maybe_private_p, int mapping_anon_p, int mapping_file_p, - const char *filename, ULONGEST addr, ULONGEST offset) +static bool +dump_mapping_p (filter_flags filterflags, const smaps_data &map) { + /* Older Linux kernels did not support the "Anonymous:" counter. + If it is missing, we can't be sure what to dump, so dump everything. */ + if (!map.has_anonymous) + return true; + /* Initially, we trust in what we received from our caller. This value may not be very precise (i.e., it was probably gathered from the permission line in the /proc/PID/smaps list, which @@ -687,41 +692,42 @@ dump_mapping_p (filter_flags filterflags, const struct smaps_vmflags *v, what we have until we take a look at the "VmFlags:" field (assuming that the version of the Linux kernel being used supports it, of course). */ - int private_p = maybe_private_p; - int dump_p; + int private_p = map.priv; /* We always dump vDSO and vsyscall mappings, because it's likely that there'll be no file to read the contents from at core load time. The kernel does the same. */ - if (strcmp ("[vdso]", filename) == 0 - || strcmp ("[vsyscall]", filename) == 0) - return 1; + if (map.filename == "[vdso]" || map.filename == "[vsyscall]") + return true; - if (v->initialized_p) + if (map.vmflags.initialized_p) { /* We never dump I/O mappings. */ - if (v->io_page) - return 0; + if (map.vmflags.io_page) + return false; /* Check if we should exclude this mapping. */ - if (!dump_excluded_mappings && v->exclude_coredump) - return 0; + if (!dump_excluded_mappings && map.vmflags.exclude_coredump) + return false; /* Update our notion of whether this mapping is shared or private based on a trustworthy value. */ - private_p = !v->shared_mapping; + private_p = !map.vmflags.shared_mapping; /* HugeTLB checking. */ - if (v->uses_huge_tlb) + if (map.vmflags.uses_huge_tlb) { if ((private_p && (filterflags & COREFILTER_HUGETLB_PRIVATE)) || (!private_p && (filterflags & COREFILTER_HUGETLB_SHARED))) - return 1; + return true; - return 0; + return false; } } + int mapping_anon_p = map.mapping_anon_p; + int mapping_file_p = map.mapping_file_p; + bool dump_p; if (private_p) { if (mapping_anon_p && mapping_file_p) @@ -761,7 +767,7 @@ dump_mapping_p (filter_flags filterflags, const struct smaps_vmflags *v, A mapping contains an ELF header if it is a private mapping, its offset is zero, and its first word is ELFMAG. */ - if (!dump_p && private_p && offset == 0 + if (!dump_p && private_p && map.offset == 0 && (filterflags & COREFILTER_ELF_HEADERS) != 0) { /* Useful define specifying the size of the ELF magical @@ -772,7 +778,7 @@ dump_mapping_p (filter_flags filterflags, const struct smaps_vmflags *v, /* Let's check if we have an ELF header. */ gdb_byte h[SELFMAG]; - if (target_read_memory (addr, h, SELFMAG) == 0) + if (target_read_memory (map.start_address, h, SELFMAG) == 0) { /* The EI_MAG* and ELFMAG* constants come from <elf/common.h>. */ @@ -781,7 +787,7 @@ dump_mapping_p (filter_flags filterflags, const struct smaps_vmflags *v, { /* This mapping contains an ELF header, so we should dump it. */ - dump_p = 1; + dump_p = true; } } } @@ -792,20 +798,24 @@ dump_mapping_p (filter_flags filterflags, const struct smaps_vmflags *v, /* As above, but return true only when we should dump the NT_FILE entry. */ -static int -dump_note_entry_p (filter_flags filterflags, const struct smaps_vmflags *v, - int maybe_private_p, int mapping_anon_p, int mapping_file_p, - const char *filename, ULONGEST addr, ULONGEST offset) +static bool +dump_note_entry_p (filter_flags filterflags, const smaps_data &map) { - /* vDSO and vsyscall mappings will end up in the core file. Don't - put them in the NT_FILE note. */ - if (strcmp ("[vdso]", filename) == 0 - || strcmp ("[vsyscall]", filename) == 0) - return 0; + /* No NT_FILE entry for mappings with no filename. */ + if (map.filename.length () == 0) + return false; + + /* Special kernel mappings, those with names like '[vdso]' and + '[vsyscall]' will be placed in the core file, but shouldn't get an + NT_FILE entry. These special mappings all have a zero inode. */ + if (map.inode == 0 + && map.filename.front () == '[' + && map.filename.back () == ']') + return false; /* Otherwise, any other file-based mapping should be placed in the note. */ - return 1; + return true; } /* Implement the "info proc" command. */ @@ -852,7 +862,7 @@ linux_info_proc (struct gdbarch *gdbarch, const char *args, { xsnprintf (filename, sizeof filename, "/proc/%ld/cmdline", pid); gdb_byte *buffer; - ssize_t len = target_fileio_read_alloc (NULL, filename, &buffer); + LONGEST len = target_fileio_read_alloc (nullptr, filename, &buffer); if (len > 0) { @@ -897,51 +907,39 @@ linux_info_proc (struct gdbarch *gdbarch, const char *args, = target_fileio_read_stralloc (NULL, filename); if (map != NULL) { - char *line; - gdb_printf (_("Mapped address spaces:\n\n")); - if (gdbarch_addr_bit (gdbarch) == 32) - { - gdb_printf ("\t%10s %10s %10s %10s %s %s\n", - "Start Addr", " End Addr", " Size", - " Offset", "Perms ", "objfile"); - } - else - { - gdb_printf (" %18s %18s %10s %10s %s %s\n", - "Start Addr", " End Addr", " Size", - " Offset", "Perms ", "objfile"); - } + ui_out_emit_table emitter (current_uiout, 6, -1, "ProcMappings"); + + int width = gdbarch_addr_bit (gdbarch) == 32 ? 10 : 18; + current_uiout->table_header (width, ui_left, "start", "Start Addr"); + current_uiout->table_header (width, ui_left, "end", "End Addr"); + current_uiout->table_header (width, ui_left, "size", "Size"); + current_uiout->table_header (width, ui_left, "offset", "Offset"); + current_uiout->table_header (5, ui_left, "perms", "Perms"); + current_uiout->table_header (0, ui_left, "objfile", "File"); + current_uiout->table_body (); char *saveptr; - for (line = strtok_r (map.get (), "\n", &saveptr); - line; - line = strtok_r (NULL, "\n", &saveptr)) + for (const char *line = strtok_r (map.get (), "\n", &saveptr); + line != nullptr; + line = strtok_r (nullptr, "\n", &saveptr)) { struct mapping m = read_mapping (line); - if (gdbarch_addr_bit (gdbarch) == 32) - { - gdb_printf ("\t%10s %10s %10s %10s %-5.*s %s\n", - paddress (gdbarch, m.addr), - paddress (gdbarch, m.endaddr), - hex_string (m.endaddr - m.addr), - hex_string (m.offset), - (int) m.permissions.size (), - m.permissions.data (), - m.filename); - } - else - { - gdb_printf (" %18s %18s %10s %10s %-5.*s %s\n", - paddress (gdbarch, m.addr), - paddress (gdbarch, m.endaddr), - hex_string (m.endaddr - m.addr), - hex_string (m.offset), - (int) m.permissions.size (), - m.permissions.data (), - m.filename); - } + ui_out_emit_tuple tuple_emitter (current_uiout, nullptr); + current_uiout->field_core_addr ("start", gdbarch, m.addr); + current_uiout->field_core_addr ("end", gdbarch, m.endaddr); + /* These next two aren't really addresses and so + shouldn't be styled as such. */ + current_uiout->field_string ("size", + paddress (gdbarch, + m.endaddr - m.addr)); + current_uiout->field_string ("offset", + paddress (gdbarch, m.offset)); + current_uiout->field_string ("perms", m.permissions); + current_uiout->field_string ("objfile", m.filename, + file_name_style.style ()); + current_uiout->text ("\n"); } } else @@ -1083,7 +1081,7 @@ linux_info_proc (struct gdbarch *gdbarch, const char *args, gdb_printf (_("Ignored signals bitmap: %s\n"), hex_string (strtoulst (p, &p, 10))); if (*p) - gdb_printf (_("Catched signals bitmap: %s\n"), + gdb_printf (_("Caught signals bitmap: %s\n"), hex_string (strtoulst (p, &p, 10))); if (*p) gdb_printf (_("wchan (system call): %s\n"), @@ -1199,7 +1197,7 @@ linux_read_core_file_mappings warning (_("malformed note - filename area is too big")); const bfd_build_id *orig_build_id = cbfd->build_id; - std::unordered_map<ULONGEST, const bfd_build_id *> vma_map; + gdb::unordered_map<ULONGEST, const bfd_build_id *> vma_map; /* Search for solib build-ids in the core file. Each time one is found, map the start vma of the corresponding elf header to the build-id. */ @@ -1242,42 +1240,34 @@ linux_read_core_file_mappings static void linux_core_info_proc_mappings (struct gdbarch *gdbarch, const char *args) { + std::optional<ui_out_emit_table> emitter; + linux_read_core_file_mappings (gdbarch, current_program_space->core_bfd (), - [=] (ULONGEST count) + [&] (ULONGEST count) { gdb_printf (_("Mapped address spaces:\n\n")); - if (gdbarch_addr_bit (gdbarch) == 32) - { - gdb_printf ("\t%10s %10s %10s %10s %s\n", - "Start Addr", - " End Addr", - " Size", " Offset", "objfile"); - } - else - { - gdb_printf (" %18s %18s %10s %10s %s\n", - "Start Addr", - " End Addr", - " Size", " Offset", "objfile"); - } + emitter.emplace (current_uiout, 5, -1, "ProcMappings"); + int width = gdbarch_addr_bit (gdbarch) == 32 ? 10 : 18; + current_uiout->table_header (width, ui_left, "start", "Start Addr"); + current_uiout->table_header (width, ui_left, "end", "End Addr"); + current_uiout->table_header (width, ui_left, "size", "Size"); + current_uiout->table_header (width, ui_left, "offset", "Offset"); + current_uiout->table_header (0, ui_left, "objfile", "File"); + current_uiout->table_body (); }, [=] (int num, ULONGEST start, ULONGEST end, ULONGEST file_ofs, const char *filename, const bfd_build_id *build_id) { - if (gdbarch_addr_bit (gdbarch) == 32) - gdb_printf ("\t%10s %10s %10s %10s %s\n", - paddress (gdbarch, start), - paddress (gdbarch, end), - hex_string (end - start), - hex_string (file_ofs), - filename); - else - gdb_printf (" %18s %18s %10s %10s %s\n", - paddress (gdbarch, start), - paddress (gdbarch, end), - hex_string (end - start), - hex_string (file_ofs), - filename); + ui_out_emit_tuple tuple_emitter (current_uiout, nullptr); + current_uiout->field_core_addr ("start", gdbarch, start); + current_uiout->field_core_addr ("end", gdbarch, end); + /* These next two aren't really addresses and so shouldn't be + styled as such. */ + current_uiout->field_string ("size", paddress (gdbarch, end - start)); + current_uiout->field_string ("offset", paddress (gdbarch, file_ofs)); + current_uiout->field_string ("objfile", filename, + file_name_style.style ()); + current_uiout->text ("\n"); }); } @@ -1332,21 +1322,15 @@ linux_core_xfer_siginfo (struct gdbarch *gdbarch, gdb_byte *readbuf, } typedef int linux_find_memory_region_ftype (ULONGEST vaddr, ULONGEST size, - ULONGEST offset, ULONGEST inode, + ULONGEST offset, int read, int write, int exec, int modified, bool memory_tagged, - const char *filename, + const std::string &filename, void *data); -typedef int linux_dump_mapping_p_ftype (filter_flags filterflags, - const struct smaps_vmflags *v, - int maybe_private_p, - int mapping_anon_p, - int mapping_file_p, - const char *filename, - ULONGEST addr, - ULONGEST offset); +typedef bool linux_dump_mapping_p_ftype (filter_flags filterflags, + const smaps_data &map); /* Helper function to parse the contents of /proc/<pid>/smaps into a data structure, for easy access. @@ -1608,35 +1592,15 @@ linux_find_memory_regions_full (struct gdbarch *gdbarch, for (const struct smaps_data &map : smaps) { - int should_dump_p = 0; - - 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; - } - /* Invoke the callback function to create the corefile segment. */ - if (should_dump_p) + if (should_dump_mapping_p (filterflags, map)) { func (map.start_address, map.end_address - map.start_address, - map.offset, map.inode, map.read, map.write, map.exec, + map.offset, map.read, map.write, map.exec, 1, /* MODIFIED is true because we want to dump the mapping. */ map.vmflags.memory_tagging != 0, - map.filename.c_str (), obfd); + map.filename, obfd); } } @@ -1662,10 +1626,10 @@ struct linux_find_memory_regions_data static int linux_find_memory_regions_thunk (ULONGEST vaddr, ULONGEST size, - ULONGEST offset, ULONGEST inode, + ULONGEST offset, int read, int write, int exec, int modified, bool memory_tagged, - const char *filename, void *arg) + const std::string &filename, void *arg) { struct linux_find_memory_regions_data *data = (struct linux_find_memory_regions_data *) arg; @@ -1711,8 +1675,6 @@ struct linux_make_mappings_data struct type *long_type; }; -static linux_find_memory_region_ftype linux_make_mappings_callback; - /* A callback for linux_find_memory_regions_full that updates the mappings data for linux_make_mappings_corefile_notes. @@ -1721,17 +1683,16 @@ static linux_find_memory_region_ftype linux_make_mappings_callback; static int linux_make_mappings_callback (ULONGEST vaddr, ULONGEST size, - ULONGEST offset, ULONGEST inode, + ULONGEST offset, int read, int write, int exec, int modified, bool memory_tagged, - const char *filename, void *data) + const std::string &filename, void *data) { struct linux_make_mappings_data *map_data = (struct linux_make_mappings_data *) data; gdb_byte buf[sizeof (ULONGEST)]; - if (*filename == '\0' || inode == 0) - return 0; + gdb_assert (filename.length () > 0); ++map_data->file_count; @@ -1742,7 +1703,7 @@ linux_make_mappings_callback (ULONGEST vaddr, ULONGEST size, pack_long (buf, map_data->long_type, offset); obstack_grow (map_data->data_obstack, buf, map_data->long_type->length ()); - obstack_grow_str0 (map_data->filename_obstack, filename); + obstack_grow_str0 (map_data->filename_obstack, filename.c_str ()); return 0; } @@ -1853,6 +1814,319 @@ linux_corefile_thread (struct thread_info *info, } } +/* Try to extract the inferior arguments, environment, and executable name + from core file CBFD. */ + +static core_file_exec_context +linux_corefile_parse_exec_context_1 (struct gdbarch *gdbarch, bfd *cbfd) +{ + gdb_assert (gdbarch != nullptr); + + /* If there's no core file loaded then we're done. */ + if (cbfd == nullptr) + return {}; + + /* This function (currently) assumes the stack grows down. If this is + not the case then this function isn't going to help. */ + if (!gdbarch_stack_grows_down (gdbarch)) + return {}; + + int ptr_bytes = gdbarch_ptr_bit (gdbarch) / TARGET_CHAR_BIT; + + /* Find the .auxv section in the core file. The BFD library creates this + for us from the AUXV note when the BFD is opened. If the section + can't be found then there's nothing more we can do. */ + struct bfd_section * section = bfd_get_section_by_name (cbfd, ".auxv"); + if (section == nullptr) + return {}; + + /* Grab the contents of the .auxv section. If we can't get the contents + then there's nothing more we can do. */ + bfd_size_type size = bfd_section_size (section); + if (bfd_section_size_insane (cbfd, section)) + return {}; + gdb::byte_vector contents (size); + if (!bfd_get_section_contents (cbfd, section, contents.data (), 0, size)) + return {}; + + /* Parse the .auxv section looking for the AT_EXECFN attribute. The + value of this attribute is a pointer to a string, the string is the + executable command. Additionally, this string is placed at the top of + the program stack, and so will be in the same PT_LOAD segment as the + argv and envp arrays. We can use this to try and locate these arrays. + If we can't find the AT_EXECFN attribute then we're not going to be + able to do anything else here. */ + CORE_ADDR execfn_string_addr; + if (target_auxv_search (contents, current_inferior ()->top_target (), + gdbarch, AT_EXECFN, &execfn_string_addr) != 1) + return {}; + + /* Read in the program headers from CBFD. If we can't do this for any + reason then just give up. */ + long phdrs_size = bfd_get_elf_phdr_upper_bound (cbfd); + if (phdrs_size == -1) + return {}; + gdb::unique_xmalloc_ptr<Elf_Internal_Phdr> + phdrs ((Elf_Internal_Phdr *) xmalloc (phdrs_size)); + int num_phdrs = bfd_get_elf_phdrs (cbfd, phdrs.get ()); + if (num_phdrs == -1) + return {}; + + /* Now scan through the headers looking for the one which contains the + address held in EXECFN_STRING_ADDR, this is the address of the + executable command pointed too by the AT_EXECFN auxv entry. */ + Elf_Internal_Phdr *hdr = nullptr; + for (int i = 0; i < num_phdrs; i++) + { + /* The program header that contains the address EXECFN_STRING_ADDR + should be one where all content is contained within CBFD, hence + the check that the file size matches the memory size. */ + if (phdrs.get ()[i].p_type == PT_LOAD + && phdrs.get ()[i].p_vaddr <= execfn_string_addr + && (phdrs.get ()[i].p_vaddr + + phdrs.get ()[i].p_memsz) > execfn_string_addr + && phdrs.get ()[i].p_memsz == phdrs.get ()[i].p_filesz) + { + hdr = &phdrs.get ()[i]; + break; + } + } + + /* If we failed to find a suitable program header then give up. */ + if (hdr == nullptr) + return {}; + + /* As we assume the stack grows down (see early check in this function) + we know that the information we are looking for sits somewhere between + EXECFN_STRING_ADDR and the segments virtual address. These define + the HIGH and LOW addresses between which we are going to search. */ + CORE_ADDR low = hdr->p_vaddr; + CORE_ADDR high = execfn_string_addr; + + /* This PTR is going to be the address we are currently accessing. */ + CORE_ADDR ptr = align_down (high, ptr_bytes); + + /* Setup DEREF a helper function which loads a value from an address. + The returned value is always placed into a uint64_t, even if we only + load 4-bytes, this allows the code below to be pretty generic. All + the values we're dealing with are unsigned, so this should be OK. */ + enum bfd_endian byte_order = gdbarch_byte_order (gdbarch); + const auto deref = [=] (CORE_ADDR p) -> uint64_t + { + ULONGEST value = read_memory_unsigned_integer (p, ptr_bytes, byte_order); + return (uint64_t) value; + }; + + /* Now search down through memory looking for a PTR_BYTES sized object + which contains the value EXECFN_STRING_ADDR. The hope is that this + will be the AT_EXECFN entry in the auxv table. There is no guarantee + that we'll find the auxv table this way, but we will do our best to + validate that what we find is the auxv table, see below. */ + while (ptr > low) + { + if (deref (ptr) == execfn_string_addr + && (ptr - ptr_bytes) > low + && deref (ptr - ptr_bytes) == AT_EXECFN) + break; + + ptr -= ptr_bytes; + } + + /* If we reached the lower bound then we failed -- bail out. */ + if (ptr <= low) + return {}; + + /* Assuming that we are looking at a value field in the auxv table, move + forward PTR_BYTES bytes so we are now looking at the next key field in + the auxv table, then scan forward until we find the null entry which + will be the last entry in the auxv table. */ + ptr += ptr_bytes; + while ((ptr + (2 * ptr_bytes)) < high + && (deref (ptr) != 0 || deref (ptr + ptr_bytes) != 0)) + ptr += (2 * ptr_bytes); + + /* PTR now points to the null entry in the auxv table, or we think it + does. Now we want to find the start of the auxv table. There's no + in-memory pattern we can search for at the start of the table, but + we can find the start based on the size of the .auxv section within + the core file CBFD object. In the actual core file the auxv is held + in a note, but the bfd library makes this into a section for us. + + The addition of (2 * PTR_BYTES) here is because PTR is pointing at the + null entry, but the null entry is also included in CONTENTS. */ + ptr = ptr + (2 * ptr_bytes) - contents.size (); + + /* If we reached the lower bound then we failed -- bail out. */ + if (ptr <= low) + return {}; + + /* PTR should now be pointing to the start of the auxv table mapped into + the inferior memory. As we got here using a heuristic then lets + compare an auxv table sized block of inferior memory, if this matches + then it's not a guarantee that we are in the right place, but it does + make it more likely. */ + gdb::byte_vector target_contents (size); + if (target_read_memory (ptr, target_contents.data (), size) != 0) + memory_error (TARGET_XFER_E_IO, ptr); + if (memcmp (contents.data (), target_contents.data (), size) != 0) + return {}; + + /* We have reasonable confidence that PTR points to the start of the auxv + table. Below this should be the null terminated list of pointers to + environment strings, and below that the null terminated list of + pointers to arguments strings. After that we should find the + argument count. First, check for the null at the end of the + environment list. */ + if (deref (ptr - ptr_bytes) != 0) + return {}; + + ptr -= (2 * ptr_bytes); + while (ptr > low && deref (ptr) != 0) + ptr -= ptr_bytes; + + /* If we reached the lower bound then we failed -- bail out. */ + if (ptr <= low) + return {}; + + /* PTR is now pointing to the null entry at the end of the argument + string pointer list. We now want to scan backward to find the entire + argument list. There's no handy null marker that we can look for + here, instead, as we scan backward we look for the argument count + (argc) value which appears immediately before the argument list. + + Technically, we could have zero arguments, so the argument count would + be zero, however, we don't support this case. If we find a null entry + in the argument list before we find the argument count then we just + bail out. + + Start by moving to the last argument string pointer, we expect this + to be non-null. */ + ptr -= ptr_bytes; + uint64_t argc = 0; + while (ptr > low) + { + uint64_t val = deref (ptr); + if (val == 0) + return {}; + + if (val == argc) + break; + + /* For GNU/Linux on ARM, glibc removes argc from the stack and + replaces it with the "stack-limit". This actually means a pointer + to the first argument string. This is unfortunate, but we can + still detect this case. */ + if (val == (ptr + ptr_bytes)) + break; + + argc++; + ptr -= ptr_bytes; + } + + /* If we reached the lower bound then we failed -- bail out. */ + if (ptr <= low) + return {}; + + /* PTR is now pointing at the argument count value (or where the argument + count should be, see notes on ARM above). Move it forward so we're + pointing at the first actual argument string pointer. */ + ptr += ptr_bytes; + + /* We can now parse all of the argument strings. */ + std::vector<gdb::unique_xmalloc_ptr<char>> arguments; + + /* Skip the first argument. This is the executable command, but we'll + load that separately later. */ + ptr += ptr_bytes; + + uint64_t v; + while ((v = deref (ptr)) != 0) + { + gdb::unique_xmalloc_ptr<char> str = target_read_string (v, INT_MAX); + if (str == nullptr) + return {}; + arguments.emplace_back (std::move (str)); + ptr += ptr_bytes; + } + + /* Skip the null-pointer at the end of the argument list. We will now + be pointing at the first environment string. */ + ptr += ptr_bytes; + + /* Parse the environment strings. */ + std::vector<gdb::unique_xmalloc_ptr<char>> environment; + while ((v = deref (ptr)) != 0) + { + gdb::unique_xmalloc_ptr<char> str = target_read_string (v, INT_MAX); + if (str == nullptr) + return {}; + environment.emplace_back (std::move (str)); + ptr += ptr_bytes; + } + + gdb::unique_xmalloc_ptr<char> execfn + = target_read_string (execfn_string_addr, INT_MAX); + if (execfn == nullptr) + return {}; + + /* When the core-file was loaded GDB processed the file backed mappings + (from the NT_FILE note). One of these should have been for the + executable. The AT_EXECFN string might not be an absolute path, but + the path in NT_FILE will be absolute, though if AT_EXECFN is a + symlink, then the NT_FILE entry will point to the actual file, not the + symlink. + + Use the AT_ENTRY address to look for the NT_FILE entry which contains + that address, this should be the executable. */ + gdb::unique_xmalloc_ptr<char> exec_filename; + CORE_ADDR exec_entry_addr; + if (target_auxv_search (contents, current_inferior ()->top_target (), + gdbarch, AT_ENTRY, &exec_entry_addr) == 1) + { + std::optional<core_target_mapped_file_info> info + = core_target_find_mapped_file (nullptr, exec_entry_addr); + if (info.has_value () && !info->filename ().empty () + && IS_ABSOLUTE_PATH (info->filename ().c_str ())) + exec_filename = make_unique_xstrdup (info->filename ().c_str ()); + } + + return core_file_exec_context (std::move (execfn), + std::move (exec_filename), + std::move (arguments), + std::move (environment)); +} + +/* Parse and return execution context details from core file CBFD. */ + +static core_file_exec_context +linux_corefile_parse_exec_context (struct gdbarch *gdbarch, bfd *cbfd) +{ + /* Catch and discard memory errors. + + If the core file format is not as we expect then we can easily trigger + a memory error while parsing the core file. We don't want this to + prevent the user from opening the core file; the information provided + by this function is helpful, but not critical, debugging can continue + without it. Instead just give a warning and return an empty context + object. */ + try + { + return linux_corefile_parse_exec_context_1 (gdbarch, cbfd); + } + catch (const gdb_exception_error &ex) + { + if (ex.error == MEMORY_ERROR) + { + warning + (_("failed to parse execution context from corefile: %s"), + ex.message->c_str ()); + return {}; + } + else + throw; + } +} + /* Fill the PRPSINFO structure with information about the process being debugged. Returns 1 in case of success, 0 for failures. Please note that even if the structure cannot be entirely filled (e.g., GDB was unable to @@ -1885,17 +2159,17 @@ linux_fill_prpsinfo (struct elf_internal_linux_prpsinfo *p) /* The number of fields read by `sscanf'. */ int n_fields = 0; - gdb_assert (p != NULL); + gdb_assert (p != nullptr); /* Obtaining PID and filename. */ pid = inferior_ptid.pid (); xsnprintf (filename, sizeof (filename), "/proc/%d/cmdline", (int) pid); /* The full name of the program which generated the corefile. */ - gdb_byte *buf = NULL; - size_t buf_len = target_fileio_read_alloc (NULL, filename, &buf); + gdb_byte *buf = nullptr; + LONGEST buf_len = target_fileio_read_alloc (nullptr, filename, &buf); gdb::unique_xmalloc_ptr<char> fname ((char *)buf); - if (buf_len < 1 || fname.get ()[0] == '\0') + if (buf_len < 1 || fname.get () == nullptr || fname.get ()[0] == '\0') { /* No program name was read, so we won't be able to retrieve more information about the process. */ @@ -2122,7 +2396,7 @@ linux_make_corefile_notes (struct gdbarch *gdbarch, bfd *obfd, int *note_size) if (!note_data) return NULL; - /* Auxillary vector. */ + /* Auxiliary vector. */ std::optional<gdb::byte_vector> auxv = target_read_alloc (current_inferior ()->top_target (), TARGET_OBJECT_AUXV, NULL); @@ -2454,7 +2728,7 @@ linux_vsyscall_range_raw (struct gdbarch *gdbarch, struct mem_range *range) in the output, which requires scanning every thread in the thread group to check whether a VMA is actually a thread's stack. With Linux 4.4 on an Intel i7-4810MQ @ 2.80GHz, with an inferior with - a few thousand threads, (1) takes a few miliseconds, while (2) + a few thousand threads, (1) takes a few milliseconds, while (2) takes several seconds. Also note that "smaps", what we read for determining core dump mappings, is even slower than "maps". */ xsnprintf (filename, sizeof filename, "/proc/%ld/task/%ld/maps", pid, pid); @@ -2803,11 +3077,11 @@ linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch, set_gdbarch_infcall_mmap (gdbarch, linux_infcall_mmap); set_gdbarch_infcall_munmap (gdbarch, linux_infcall_munmap); set_gdbarch_get_siginfo_type (gdbarch, linux_get_siginfo_type); + set_gdbarch_core_parse_exec_context (gdbarch, + linux_corefile_parse_exec_context); } -void _initialize_linux_tdep (); -void -_initialize_linux_tdep () +INIT_GDB_FILE (linux_tdep) { /* Observers used to invalidate the cache when needed. */ gdb::observers::inferior_exit.attach (invalidate_linux_cache_inf, @@ -2840,65 +3114,5 @@ VM_DONTDUMP flag (\"dd\" in /proc/PID/smaps) when generating the corefile. For\ more information about this file, refer to the manpage of proc(5) and core(5)."), NULL, show_dump_excluded_mappings, &setlist, &showlist); -} - -/* Fetch (and possibly build) an appropriate `link_map_offsets' for - ILP32/LP64 Linux systems which don't have the r_ldsomap field. */ - -link_map_offsets * -linux_ilp32_fetch_link_map_offsets () -{ - static link_map_offsets lmo; - static link_map_offsets *lmp = nullptr; - - if (lmp == nullptr) - { - lmp = &lmo; - - lmo.r_version_offset = 0; - lmo.r_version_size = 4; - lmo.r_map_offset = 4; - lmo.r_brk_offset = 8; - lmo.r_ldsomap_offset = -1; - lmo.r_next_offset = 20; - - /* Everything we need is in the first 20 bytes. */ - lmo.link_map_size = 20; - lmo.l_addr_offset = 0; - lmo.l_name_offset = 4; - lmo.l_ld_offset = 8; - lmo.l_next_offset = 12; - lmo.l_prev_offset = 16; - } - - return lmp; -} - -link_map_offsets * -linux_lp64_fetch_link_map_offsets () -{ - static link_map_offsets lmo; - static link_map_offsets *lmp = nullptr; - - if (lmp == nullptr) - { - lmp = &lmo; - - lmo.r_version_offset = 0; - lmo.r_version_size = 4; - lmo.r_map_offset = 8; - lmo.r_brk_offset = 16; - lmo.r_ldsomap_offset = -1; - lmo.r_next_offset = 40; - - /* Everything we need is in the first 40 bytes. */ - lmo.link_map_size = 40; - lmo.l_addr_offset = 0; - lmo.l_name_offset = 8; - lmo.l_ld_offset = 16; - lmo.l_next_offset = 24; - lmo.l_prev_offset = 32; - } - return lmp; } |