summaryrefslogtreecommitdiff
path: root/debuginfod/debuginfod-client.c
diff options
context:
space:
mode:
authorAaron Merey <amerey@redhat.com>2022-10-31 23:44:23 -0400
committerAaron Merey <amerey@redhat.com>2022-11-01 00:25:15 -0400
commitfb833750c395ce268d16fd44e4decafbb41208a8 (patch)
treef756e96a54ea06ad831e11d14d2675d49c5e242e /debuginfod/debuginfod-client.c
parent5d8cd51a460fc29f35a016836c39fcea6bf59fb0 (diff)
downloadelfutils-fb833750c395ce268d16fd44e4decafbb41208a8.tar.gz
debuginfod: Support queries for ELF/DWARF sections
Add new function debuginfod_find_section which queries debuginfod servers for the raw binary contents of the specified ELF/DWARF section in a file matching the given build-id. Extend the server webapi to support section queries. Section query URLS have the following format: /buildid/BUILDID/section/SECTION The server will attempt to extract the section from a debuginfo file matching the given build-id. If the debuginfo file cannot be found or the section has type SHT_NOBITS, the server will attempt to extract the section from the executable file matching the build-id. If the server is built without section query support, the client will attempt to download the debuginfo matching the build-id and extract the section. If the debuginfo file cannot be found or the section has type SHT_NOBITS, the server will attempt to download the executable file matching the build-id and extract the section. Signed-off-by: Aaron Merey <amerey@redhat.com>
Diffstat (limited to 'debuginfod/debuginfod-client.c')
-rw-r--r--debuginfod/debuginfod-client.c363
1 files changed, 336 insertions, 27 deletions
diff --git a/debuginfod/debuginfod-client.c b/debuginfod/debuginfod-client.c
index 716cb769..d097ca49 100644
--- a/debuginfod/debuginfod-client.c
+++ b/debuginfod/debuginfod-client.c
@@ -45,6 +45,7 @@
#include <ctype.h>
#include <errno.h>
#include <stdlib.h>
+#include <gelf.h>
/* We might be building a bootstrap dummy library, which is really simple. */
#ifdef DUMMY_LIBDEBUGINFOD
@@ -56,6 +57,9 @@ int debuginfod_find_executable (debuginfod_client *c, const unsigned char *b,
int s, char **p) { return -ENOSYS; }
int debuginfod_find_source (debuginfod_client *c, const unsigned char *b,
int s, const char *f, char **p) { return -ENOSYS; }
+int debuginfod_find_section (debuginfod_client *c, const unsigned char *b,
+ int s, const char *scn, char **p)
+ { return -ENOSYS; }
void debuginfod_set_progressfn(debuginfod_client *c,
debuginfod_progressfn_t fn) { }
void debuginfod_set_verbose_fd(debuginfod_client *c, int fd) { }
@@ -130,6 +134,9 @@ struct debuginfod_client
debuginfod_end needs to terminate. */
int default_progressfn_printed_p;
+ /* Indicates whether the last query was cancelled by progressfn. */
+ bool progressfn_cancel;
+
/* File descriptor to output any verbose messages if > 0. */
int verbose_fd;
@@ -576,21 +583,257 @@ header_callback (char * buffer, size_t size, size_t numitems, void * userdata)
return numitems;
}
+/* Copy SRC to DEST, s,/,#,g */
+
+static void
+path_escape (const char *src, char *dest)
+{
+ unsigned q = 0;
+
+ for (unsigned fi=0; q < PATH_MAX-2; fi++) /* -2, escape is 2 chars. */
+ switch (src[fi])
+ {
+ case '\0':
+ dest[q] = '\0';
+ q = PATH_MAX-1; /* escape for loop too */
+ break;
+ case '/': /* escape / to prevent dir escape */
+ dest[q++]='#';
+ dest[q++]='#';
+ break;
+ case '#': /* escape # to prevent /# vs #/ collisions */
+ dest[q++]='#';
+ dest[q++]='_';
+ break;
+ default:
+ dest[q++]=src[fi];
+ }
+
+ dest[q] = '\0';
+}
+
+/* Attempt to read an ELF/DWARF section with name SECTION from FD and write
+ it to a separate file in the debuginfod cache. If successful the absolute
+ path of the separate file containing SECTION will be stored in USR_PATH.
+ FD_PATH is the absolute path for FD.
+
+ If the section cannot be extracted, then return a negative error code.
+ -ENOENT indicates that the parent file was able to be read but the
+ section name was not found. -EEXIST indicates that the section was
+ found but had type SHT_NOBITS. */
+
+int
+extract_section (int fd, const char *section, char *fd_path, char **usr_path)
+{
+ elf_version (EV_CURRENT);
+
+ Elf *elf = elf_begin (fd, ELF_C_READ_MMAP_PRIVATE, NULL);
+ if (elf == NULL)
+ return -EIO;
+
+ size_t shstrndx;
+ int rc = elf_getshdrstrndx (elf, &shstrndx);
+ if (rc < 0)
+ {
+ rc = -EIO;
+ goto out;
+ }
+
+ int sec_fd = -1;
+ char *escaped_name = NULL;
+ char *sec_path_tmp = NULL;
+ Elf_Scn *scn = NULL;
+
+ /* Try to find the target section and copy the contents into a
+ separate file. */
+ while (true)
+ {
+ scn = elf_nextscn (elf, scn);
+ if (scn == NULL)
+ {
+ rc = -ENOENT;
+ goto out;
+ }
+ GElf_Shdr shdr_storage;
+ GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_storage);
+ if (shdr == NULL)
+ {
+ rc = -EIO;
+ goto out;
+ }
+
+ const char *scn_name = elf_strptr (elf, shstrndx, shdr->sh_name);
+ if (scn_name == NULL)
+ {
+ rc = -EIO;
+ goto out;
+ }
+ if (strcmp (scn_name, section) == 0)
+ {
+ /* We found the desired section. */
+ if (shdr->sh_type == SHT_NOBITS)
+ {
+ rc = -EEXIST;
+ goto out;
+ }
+
+ Elf_Data *data = NULL;
+ data = elf_rawdata (scn, NULL);
+ if (data == NULL)
+ {
+ rc = -EIO;
+ goto out;
+ }
+
+ if (data->d_buf == NULL)
+ {
+ rc = -EIO;
+ goto out;
+ }
+
+ /* Compute the absolute filename we'll write the section to.
+ Replace the last component of FD_PATH with the path-escaped
+ section filename. */
+ int i = strlen (fd_path);
+ while (i >= 0)
+ {
+ if (fd_path[i] == '/')
+ {
+ fd_path[i] = '\0';
+ break;
+ }
+ --i;
+ }
+
+ escaped_name = malloc (strlen (section) * 2 + 1);
+ if (escaped_name == NULL)
+ {
+ rc = -ENOMEM;
+ goto out;
+ }
+ path_escape (section, escaped_name);
+
+ rc = asprintf (&sec_path_tmp, "%s/section-%s.XXXXXX",
+ fd_path, escaped_name);
+ if (rc == -1)
+ {
+ rc = -ENOMEM;
+ goto out1;
+ }
+
+ sec_fd = mkstemp (sec_path_tmp);
+ if (sec_fd < 0)
+ {
+ rc = -EIO;
+ goto out2;
+ }
+
+ ssize_t res = write_retry (sec_fd, data->d_buf, data->d_size);
+ if (res < 0 || (size_t) res != data->d_size)
+ {
+ rc = -EIO;
+ goto out3;
+ }
+
+ /* Success. Rename tmp file and update USR_PATH. */
+ char *sec_path;
+ if (asprintf (&sec_path, "%s/section-%s", fd_path, section) == -1)
+ {
+ rc = -ENOMEM;
+ goto out3;
+ }
+
+ rc = rename (sec_path_tmp, sec_path);
+ if (rc < 0)
+ {
+ free (sec_path);
+ rc = -EIO;
+ goto out3;
+ }
+
+ if (usr_path != NULL)
+ *usr_path = sec_path;
+ else
+ free (sec_path);
+ rc = sec_fd;
+ goto out2;
+ }
+ }
+
+out3:
+ close (sec_fd);
+ unlink (sec_path_tmp);
+
+out2:
+ free (sec_path_tmp);
+
+out1:
+ free (escaped_name);
+
+out:
+ elf_end (elf);
+ return rc;
+}
+
+/* Search TARGET_CACHE_DIR for a debuginfo or executable file containing
+ an ELF/DWARF section with name SCN_NAME. If found, extract the section
+ to a separate file in TARGET_CACHE_DIR and return a file descriptor
+ for the section file. The path for this file will be stored in USR_PATH.
+ Return a negative errno if unsuccessful. */
+
+static int
+cache_find_section (const char *scn_name, const char *target_cache_dir,
+ char **usr_path)
+{
+ int fd;
+ int rc = -EEXIST;
+ char parent_path[PATH_MAX];
+
+ /* Check the debuginfo first. */
+ snprintf (parent_path, PATH_MAX, "%s/debuginfo", target_cache_dir);
+ fd = open (parent_path, O_RDONLY);
+ if (fd >= 0)
+ {
+ rc = extract_section (fd, scn_name, parent_path, usr_path);
+ close (fd);
+ }
+
+ /* If the debuginfo file couldn't be found or the section type was
+ SHT_NOBITS, check the executable. */
+ if (rc == -EEXIST)
+ {
+ snprintf (parent_path, PATH_MAX, "%s/executable", target_cache_dir);
+ fd = open (parent_path, O_RDONLY);
+
+ if (fd >= 0)
+ {
+ rc = extract_section (fd, scn_name, parent_path, usr_path);
+ close (fd);
+ }
+ }
+
+ return rc;
+}
+
/* Query each of the server URLs found in $DEBUGINFOD_URLS for the file
- with the specified build-id, type (debuginfo, executable or source)
- and filename. filename may be NULL. If found, return a file
- descriptor for the target, otherwise return an error code.
+ with the specified build-id and type (debuginfo, executable, source or
+ section). If type is source, then type_arg should be a filename. If
+ type is section, then type_arg should be the name of an ELF/DWARF
+ section. Otherwise type_arg may be NULL. Return a file descriptor
+ for the target if successful, otherwise return an error code.
*/
static int
debuginfod_query_server (debuginfod_client *c,
const unsigned char *build_id,
int build_id_len,
const char *type,
- const char *filename,
+ const char *type_arg,
char **path)
{
char *server_urls;
char *urls_envvar;
+ const char *section = NULL;
+ const char *filename = NULL;
char *cache_path = NULL;
char *maxage_path = NULL;
char *interval_path = NULL;
@@ -603,6 +846,17 @@ debuginfod_query_server (debuginfod_client *c,
int vfd = c->verbose_fd;
int rc;
+ c->progressfn_cancel = false;
+
+ if (strcmp (type, "source") == 0)
+ filename = type_arg;
+ else if (strcmp (type, "section") == 0)
+ {
+ section = type_arg;
+ if (section == NULL)
+ return -EINVAL;
+ }
+
if (vfd >= 0)
{
dprintf (vfd, "debuginfod_find_%s ", type);
@@ -701,31 +955,13 @@ debuginfod_query_server (debuginfod_client *c,
goto out;
}
- /* copy the filename to suffix, s,/,#,g */
- unsigned q = 0;
- for (unsigned fi=0; q < PATH_MAX-2; fi++) /* -2, escape is 2 chars. */
- switch (filename[fi])
- {
- case '\0':
- suffix[q] = '\0';
- q = PATH_MAX-1; /* escape for loop too */
- break;
- case '/': /* escape / to prevent dir escape */
- suffix[q++]='#';
- suffix[q++]='#';
- break;
- case '#': /* escape # to prevent /# vs #/ collisions */
- suffix[q++]='#';
- suffix[q++]='_';
- break;
- default:
- suffix[q++]=filename[fi];
- }
- suffix[q] = '\0';
+ path_escape (filename, suffix);
/* If the DWARF filenames are super long, this could exceed
PATH_MAX and truncate/collide. Oh well, that'll teach
them! */
}
+ else if (section != NULL)
+ path_escape (section, suffix);
else
suffix[0] = '\0';
@@ -797,7 +1033,10 @@ debuginfod_query_server (debuginfod_client *c,
}
xalloc_str (target_cache_dir, "%s/%s", cache_path, build_id_bytes);
- xalloc_str (target_cache_path, "%s/%s%s", target_cache_dir, type, suffix);
+ if (section != NULL)
+ xalloc_str (target_cache_path, "%s/%s-%s", target_cache_dir, type, suffix);
+ else
+ xalloc_str (target_cache_path, "%s/%s%s", target_cache_dir, type, suffix);
xalloc_str (target_cache_tmppath, "%s.XXXXXX", target_cache_path);
/* XXX combine these */
@@ -881,6 +1120,18 @@ debuginfod_query_server (debuginfod_client *c,
/* Ensure old 000-permission files are not lingering in the cache. */
unlink(target_cache_path);
+ if (section != NULL)
+ {
+ /* Try to extract the section from a cached file before querying
+ any servers. */
+ rc = cache_find_section (section, target_cache_dir, path);
+
+ /* If the section was found or confirmed to not exist, then we
+ are done. */
+ if (rc >= 0 || rc == -ENOENT)
+ goto out;
+ }
+
long timeout = default_timeout;
const char* timeout_envvar = getenv(DEBUGINFOD_TIMEOUT_ENV_VAR);
if (timeout_envvar != NULL)
@@ -1053,6 +1304,9 @@ debuginfod_query_server (debuginfod_client *c,
snprintf(data[i].url, PATH_MAX, "%s/%s/%s/%s", server_url,
build_id_bytes, type, escaped_string);
}
+ else if (section)
+ snprintf(data[i].url, PATH_MAX, "%s/%s/%s/%s", server_url,
+ build_id_bytes, type, section);
else
snprintf(data[i].url, PATH_MAX, "%s/%s/%s", server_url, build_id_bytes, type);
if (vfd >= 0)
@@ -1257,7 +1511,10 @@ debuginfod_query_server (debuginfod_client *c,
}
if ((*c->progressfn) (c, pa, dl_size))
- break;
+ {
+ c->progressfn_cancel = true;
+ break;
+ }
}
/* Check to see if we are downloading something which exceeds maxsize, if set.*/
@@ -1324,6 +1581,8 @@ debuginfod_query_server (debuginfod_client *c,
/* 406 signals that the requested file was too large */
if ( ok0 == CURLE_OK && resp_code == 406)
rc = -EFBIG;
+ else if (section != NULL && resp_code == 503)
+ rc = -EINVAL;
else
rc = -ENOENT;
break;
@@ -1649,6 +1908,56 @@ int debuginfod_find_source(debuginfod_client *client,
"source", filename, path);
}
+int
+debuginfod_find_section (debuginfod_client *client,
+ const unsigned char *build_id, int build_id_len,
+ const char *section, char **path)
+{
+ int rc = debuginfod_query_server(client, build_id, build_id_len,
+ "section", section, path);
+ if (rc != -EINVAL)
+ return rc;
+
+ /* The servers may have lacked support for section queries. Attempt to
+ download the debuginfo or executable containing the section in order
+ to extract it. */
+ rc = -EEXIST;
+ int fd = -1;
+ char *tmp_path = NULL;
+
+ fd = debuginfod_find_debuginfo (client, build_id, build_id_len, &tmp_path);
+ if (client->progressfn_cancel)
+ {
+ if (fd >= 0)
+ {
+ /* This shouldn't happen, but we'll check this condition
+ just in case. */
+ close (fd);
+ free (tmp_path);
+ }
+ return -ENOENT;
+ }
+ if (fd > 0)
+ {
+ rc = extract_section (fd, section, tmp_path, path);
+ close (fd);
+ }
+
+ if (rc == -EEXIST)
+ {
+ /* The section should be found in the executable. */
+ fd = debuginfod_find_executable (client, build_id,
+ build_id_len, &tmp_path);
+ if (fd > 0)
+ {
+ rc = extract_section (fd, section, tmp_path, path);
+ close (fd);
+ }
+ }
+
+ free (tmp_path);
+ return rc;
+}
/* Add an outgoing HTTP header. */
int debuginfod_add_http_header (debuginfod_client *client, const char* header)