Skip to content
Snippets Groups Projects
elf-utils.c 4.56 KiB
/*
 * Copyright © 2017-2020 Collabora Ltd.
 *
 * SPDX-License-Identifier: LGPL-2.1-or-later
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
 */

#include "elf-utils.h"

#include <glib.h>
#include <glib/gstdio.h>
#include <gio/gio.h>
#include <libelf.h>

#include "libglnx/libglnx.h"

#include "steam-runtime-tools/glib-backports-internal.h"

#define throw_elf_error(error, format, ...) \
  glnx_null_throw (error, format ": %s", ##__VA_ARGS__, elf_errmsg (elf_errno ()))

/*
 * pv_elf_open_fd:
 * @fd: An open file descriptor
 *
 * Returns: (transfer full): A libelf object representing the library
 */
Elf *
pv_elf_open_fd (int fd,
                GError **error)
{
  g_autoptr(Elf) elf = NULL;

  g_return_val_if_fail (fd >= 0, NULL);
  g_return_val_if_fail (error == NULL || *error == NULL, NULL);

  if (elf_version (EV_CURRENT) == EV_NONE)
    return throw_elf_error (error, "elf_version(EV_CURRENT)");

  elf = elf_begin (fd, ELF_C_READ, NULL);

  if (elf == NULL)
    return throw_elf_error (error, "elf_begin");

  return g_steal_pointer (&elf);
}

/*
 * pv_elf_get_soname:
 * @elf: A libelf object
 *
 * Return the `DT_SONAME` header, or %NULL on error
 */
gchar *
pv_elf_get_soname (Elf *elf,
                   GError **error)
{
  GElf_Ehdr ehdr;
  size_t phdr_count = 0;
  size_t i;
  GElf_Phdr phdr_mem;
  GElf_Phdr *phdr = NULL;
  GElf_Dyn *dynamic_header = NULL;
  GElf_Dyn dynamic_header_mem;
  Elf_Scn *dynamic_section = NULL;
  GElf_Shdr dynamic_section_header_mem;
  GElf_Shdr *dynamic_section_header = NULL;
  Elf_Scn *string_table_section = NULL;
  GElf_Shdr string_table_header_mem;
  GElf_Shdr *string_table_header = NULL;
  Elf_Data *data = NULL;
  size_t size_per_dyn;
  const char *soname = NULL;

  g_return_val_if_fail (elf != NULL, NULL);
  g_return_val_if_fail (error == NULL || *error == NULL, NULL);

  if (elf_kind (elf) != ELF_K_ELF)
    return glnx_null_throw (error, "elf_kind %d, expected ELF_K_ELF=%d",
                            elf_kind (elf), ELF_K_ELF);

  if (gelf_getehdr (elf, &ehdr) == NULL)
    return throw_elf_error (error, "elf_getehdr");

  if (ehdr.e_type != ET_DYN)
    return glnx_null_throw (error, "ehdr.e_type %d, expected ET_DYN=%d",
                            ehdr.e_type, ET_DYN);

  if (elf_getphdrnum (elf, &phdr_count) != 0)
    return throw_elf_error (error, "elf_getphdrnum");

  for (i = 0; i < phdr_count; i++)
    {
      phdr = gelf_getphdr (elf, i, &phdr_mem);

      if (phdr != NULL && phdr->p_type == PT_DYNAMIC)
        {
          dynamic_section = gelf_offscn (elf, phdr->p_offset);
          dynamic_section_header = gelf_getshdr (dynamic_section,
                                                 &dynamic_section_header_mem);
          break;
        }
    }

  if (dynamic_section == NULL || dynamic_section_header == NULL)
    return glnx_null_throw (error, "Unable to find dynamic section header");

  string_table_section = elf_getscn (elf, dynamic_section_header->sh_link);
  string_table_header = gelf_getshdr (string_table_section,
                                      &string_table_header_mem);

  if (string_table_section == NULL || string_table_header == NULL)
    return glnx_null_throw (error, "Unable to find linked string table");

  data = elf_getdata (dynamic_section, NULL);

  if (data == NULL)
    return throw_elf_error (error, "elf_getdata(dynamic_section)");

  size_per_dyn = gelf_fsize (elf, ELF_T_DYN, 1, EV_CURRENT);

  for (i = 0; i < dynamic_section_header->sh_size / size_per_dyn; i++)
    {
      dynamic_header = gelf_getdyn (data, i, &dynamic_header_mem);

      if (dynamic_header == NULL)
        break;
      if (dynamic_header->d_tag == DT_SONAME)
        {
          soname = elf_strptr (elf, dynamic_section_header->sh_link,
                               dynamic_header->d_un.d_val);

          if (soname == NULL)
            return glnx_null_throw (error, "Unable to read DT_SONAME");


        }
    }

  if (soname == NULL)
    return glnx_null_throw (error, "Unable to find DT_SONAME");

  return g_strdup (soname);
}