From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: (qmail 1511 invoked by alias); 12 Apr 2019 15:38:16 -0000 Mailing-List: contact elfutils-devel-help@sourceware.org; run by ezmlm Precedence: bulk List-Id: List-Post: List-Help: List-Subscribe: Sender: elfutils-devel-owner@sourceware.org Received: (qmail 1501 invoked by uid 89); 12 Apr 2019 15:38:15 -0000 Authentication-Results: sourceware.org; auth=none X-Virus-Checked: by ClamAV 0.100.3 on sourceware.org X-Virus-Found: No X-Spam-SWARE-Status: No, score=-18.4 required=5.0 tests=AWL,BAYES_00,GIT_PATCH_0,GIT_PATCH_1,GIT_PATCH_2,GIT_PATCH_3,KAM_SHORT,SPF_HELO_PASS autolearn=ham version=3.3.1 spammy=Treat, disregard X-Spam-Status: No, score=-18.4 required=5.0 tests=AWL,BAYES_00,GIT_PATCH_0,GIT_PATCH_1,GIT_PATCH_2,GIT_PATCH_3,KAM_SHORT,SPF_HELO_PASS autolearn=ham version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on sourceware.org X-Spam-Level: X-HELO: mx1.redhat.com Received: from mx1.redhat.com (HELO mx1.redhat.com) (209.132.183.28) by sourceware.org (qpsmtpd/0.93/v0.84-503-g423c35a) with ESMTP; Fri, 12 Apr 2019 15:38:13 +0000 Received: from smtp.corp.redhat.com (int-mx07.intmail.prod.int.phx2.redhat.com [10.5.11.22]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id 2E54A5946F for ; Fri, 12 Apr 2019 15:38:12 +0000 (UTC) Received: from oldenburg2.str.redhat.com (ovpn-116-234.ams2.redhat.com [10.36.116.234]) by smtp.corp.redhat.com (Postfix) with ESMTPS id 2F5081001E7A for ; Fri, 12 Apr 2019 15:38:10 +0000 (UTC) From: Florian Weimer To: elfutils-devel@sourceware.org Subject: [PATCH] elfclassify tool Date: Fri, 12 Apr 2019 15:38:00 -0000 Message-ID: <87k1fz8c9q.fsf@oldenburg2.str.redhat.com> User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/26.1 (gnu/linux) MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable X-Scanned-By: MIMEDefang 2.84 on 10.5.11.22 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.5.16 (mx1.redhat.com [10.5.110.39]); Fri, 12 Apr 2019 15:38:12 +0000 (UTC) X-IsSubscribed: yes X-SW-Source: 2019-q2/txt/msg00012.txt.bz2 This patch adds an elfclassify tool, mainly for the benefit of RPM's find-debuginfo.sh. I still need to implement an --unstripped option and fix the iteration over the dynamic section. Suggestions for improving the argp/help output are welcome as well. I'm not familiar with argp at all. I'm keeping a branch with these changes here: Thanks, Florian diff --git a/src/Makefile.am b/src/Makefile.am index 2b1c0dcb..966d1da7 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -26,7 +26,8 @@ AM_CPPFLAGS +=3D -I$(srcdir)/../libelf -I$(srcdir)/../lib= ebl \ AM_LDFLAGS =3D -Wl,-rpath-link,../libelf:../libdw =20 bin_PROGRAMS =3D readelf nm size strip elflint findtextrel addr2line \ - elfcmp objdump ranlib strings ar unstrip stack elfcompress + elfcmp objdump ranlib strings ar unstrip stack elfcompress \ + elfclassify =20 noinst_LIBRARIES =3D libar.a =20 @@ -83,6 +84,7 @@ ar_LDADD =3D libar.a $(libelf) $(libeu) $(argp_LDADD) unstrip_LDADD =3D $(libebl) $(libelf) $(libdw) $(libeu) $(argp_LDADD) -ldl stack_LDADD =3D $(libebl) $(libelf) $(libdw) $(libeu) $(argp_LDADD) -ldl $= (demanglelib) elfcompress_LDADD =3D $(libebl) $(libelf) $(libdw) $(libeu) $(argp_LDADD) +elfclassify_LDADD =3D $(libelf) $(libeu) $(argp_LDADD) =20 installcheck-binPROGRAMS: $(bin_PROGRAMS) bad=3D0; pid=3D$$$$; list=3D"$(bin_PROGRAMS)"; for p in $$list; do \ diff --git a/src/elfclassify.c b/src/elfclassify.c new file mode 100644 index 00000000..ead3260b --- /dev/null +++ b/src/elfclassify.c @@ -0,0 +1,387 @@ +/* Classification of ELF files. + Copyright (C) 2019 Red Hat, Inc. + This file is part of elfutils. + + This file is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + elfutils 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . = */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include ELFUTILS_HEADER(elf) +#include "printversion.h" + +/* Name and version of program. */ +ARGP_PROGRAM_VERSION_HOOK_DEF =3D print_version; + +/* Bug report address. */ +ARGP_PROGRAM_BUG_ADDRESS_DEF =3D PACKAGE_BUGREPORT; + +enum classify_command +{ + classify_file =3D 1000, + classify_elf, + classify_executable, + classify_shared, + classify_loadable +}; + +/* Set by parse_opt. */ +static enum classify_command command; +static const char *command_path; +static int verbose; + +/* Set by map_file. */ +static int file_fd =3D -1; + +static void +open_file (void) +{ + if (verbose > 1) + fprintf (stderr, "debug: processing file: %s\n", command_path); + + file_fd =3D open (command_path, O_RDONLY); + if (file_fd < 0) + { + if (errno =3D=3D ENOENT) + exit (1); + else + error (2, errno, N_("opening %s"), command_path); + } + struct stat st; + if (fstat (file_fd, &st) !=3D 0) + error (2, errno, N_("reading %s\n"), command_path); + if (!S_ISREG (st.st_mode)) + exit (1); +} + +/* Set by open_elf. */ +static Elf *elf; + +static void +open_elf (void) +{ + open_file (); + elf =3D elf_begin (file_fd, ELF_C_READ, NULL); + if (elf =3D=3D NULL) + error (2, 0, "%s: %s", command_path, elf_errmsg (-1)); + if (elf_kind (elf) !=3D ELF_K_ELF && elf_kind (elf) !=3D ELF_K_AR) + exit (1); +} + +static int elf_type; +static bool has_program_interpreter; +static bool has_dynamic; +static bool has_soname; +static bool has_pie_flag; +static bool has_dt_debug; + +static void +run_classify (void) +{ + if (elf_kind (elf) !=3D ELF_K_ELF) + return; + + GElf_Ehdr ehdr_storage; + GElf_Ehdr *ehdr =3D gelf_getehdr (elf, &ehdr_storage); + if (ehdr =3D=3D NULL) + exit (1); + elf_type =3D ehdr->e_type; + + /* Examine program headers. */ + { + size_t nphdrs; + if (elf_getphdrnum (elf, &nphdrs) !=3D 0) + error (2, 0, "%s: program header: %s", command_path, elf_errmsg (-1)= ); + if (nphdrs > INT_MAX) + error (2, 0, "%s: number of program headers is too large: %zu", + command_path, nphdrs); + for (size_t phdr_idx =3D 0; phdr_idx < nphdrs; ++phdr_idx) + { + GElf_Phdr phdr_storage; + GElf_Phdr *phdr =3D gelf_getphdr (elf, phdr_idx, &phdr_storage); + if (phdr =3D=3D NULL) + error (2, 0, "%s: %s", command_path, elf_errmsg (-1)); + if (phdr->p_type =3D=3D PT_DYNAMIC) + has_dynamic =3D true; + if (phdr->p_type =3D=3D PT_INTERP) + has_program_interpreter =3D true; + } + } + + /* Examine the dynamic section. */ + if (has_dynamic) + { + Elf_Scn *dyn_section =3D NULL; + { + Elf_Scn *scn =3D NULL; + while (true) + { + scn =3D elf_nextscn (elf, scn); + if (scn =3D=3D NULL) + break; + GElf_Shdr shdr_storage; + GElf_Shdr *shdr =3D gelf_getshdr (scn, &shdr_storage); + if (shdr =3D=3D NULL) + error (2, 0, N_("could not obtain section header: %s"), + elf_errmsg (-1)); + if (verbose > 2) + fprintf (stderr, "debug: section header %d found\n", + shdr->sh_type); + if (shdr->sh_type =3D=3D SHT_DYNAMIC) + { + if (verbose > 1) + fputs ("debug: dynamic section found", stderr); + dyn_section =3D scn; + break; + } + } + } + if (dyn_section !=3D NULL) + { + Elf_Data *data =3D elf_getdata (dyn_section, NULL); + if (verbose > 2) + fprintf (stderr, "debug: Elf_Data for dynamic section: %p\n", + data); + + if (data !=3D NULL) + for (int dyn_idx =3D 0; ; ++dyn_idx) + { + GElf_Dyn dyn_storage; + GElf_Dyn *dyn =3D gelf_getdyn (data, dyn_idx, &dyn_storage= ); + if (dyn =3D=3D NULL) + break; + if (verbose > 2) + fprintf (stderr, "debug: dynamic entry %d" + " with tag %llu found\n", + dyn_idx, (unsigned long long int) dyn->d_tag); + if (dyn->d_tag =3D=3D DT_SONAME) + has_soname =3D true; + if (dyn->d_tag =3D=3D DT_FLAGS_1 && (dyn->d_un.d_val & DF_= 1_PIE)) + has_pie_flag =3D true; + if (dyn->d_tag =3D=3D DT_DEBUG) + has_dt_debug =3D true; + if (dyn->d_tag =3D=3D DT_NULL) + break; + } + } + } + + if (verbose) + { + fprintf (stderr, "info: ELF type: %d\n", elf_type); + if (has_program_interpreter) + fputs ("info: program interpreter found\n", stderr); + if (has_dynamic) + fputs ("info: dynamic segment found\n", stderr); + if (has_soname) + fputs ("info: soname found\n", stderr); + if (has_pie_flag) + fputs ("info: PIE flag found\n", stderr); + if (has_dt_debug) + fputs ("info: DT_DEBUG found\n", stderr); + } +} + +/* Return true if the file is a loadable object, which basically means + it is an ELF file, but not a relocatable object file. (The kernel + and various userspace components can load ET_REL files, but we + disregard that for our classification purposes.) */ +static bool +is_loadable (void) +{ + return elf_kind (elf) =3D=3D ELF_K_ELF && elf_type !=3D ET_REL; +} + +static bool +is_shared (void) +{ + if (!is_loadable ()) + return false; + + /* The ELF type is very clear: this is an executable. */ + if (elf_type =3D=3D ET_EXEC) + return false; + + /* If the object is marked as PIE, it is definitely an executable, + and not a loadlable shared object. */ + if (has_pie_flag) + return false; + + /* Treat a DT_SONAME tag as a strong indicator that this is a shared + object. */ + if (has_soname) + return true; + + /* This is probably a PIE program: there is no soname, but a program + interpreter. In theory, this file could be also */ + if (has_program_interpreter) + return false; + + /* Roland McGrath mentions in + , + that =E2=80=9Cwe defined a PIE as an ET_DYN with a DT_DEBUG=E2=80=9D.= This + matches current binutils behavior (version 2.32). DT_DEBUG is + added if bfd_link_executable returns true or if bfd_link_pic + returns false, depending on the architectures. However, DT_DEBUG + is not documented as being specific to executables, therefore use + it only as a low-priority discriminator. */ + if (has_dt_debug) + return false; + + /* If there is no dynamic section, the file cannot be loaded as a + shared object. */ + if (!has_dynamic) + return false; + return true; +} + +static bool +is_executable (void) +{ + if (!is_loadable ()) + return false; + + /* A loadable object which is not a shared object is treated as an + executable. */ + return !is_shared (); +} + +static error_t +parse_opt (int key, char *arg, struct argp_state *state) +{ + switch (key) + { + case classify_file: + case classify_elf: + case classify_executable: + case classify_shared: + case classify_loadable: + command =3D key; + command_path =3D arg; + break; + + case 'v': + ++verbose; + break; + + case ARGP_KEY_ARG: + argp_usage (state); + exit (2); + } + + return 0; +} + +int +main (int argc, char **argv) +{ + const struct argp_option options[] =3D + { + { "file", classify_file, "PATH", 0, + N_("Check PATH is file that can be read"), 0 }, + { "elf", classify_elf, "PATH", 0, + N_("Check if the file at PATH is a valid ELF object"), 0 }, + { "executable", classify_executable, "PATH", 0, + N_("Check if the file at PATH is an ELF program executable"), 0 }, + { "shared", classify_shared, "PATH", 0, + N_("Check if the file at PATH is an ELF shared object (DSO)"), 0 }, + { "loadable", classify_loadable, "PATH", 0, + N_("Check if the file at PATH is a loadable object (program or sha= red object)"), 0 }, + { "verbose", 'v', NULL, 0, + N_("Output additional information (can be specified multiple times= )"), 0 }, + { NULL, 0, NULL, 0, NULL, 0 } + }; + + const struct argp argp =3D + { + .options =3D options, + .parser =3D parse_opt, + .doc =3D N_("Determine the type of an ELF file.") + }; + + if (argp_parse (&argp, argc, argv, ARGP_NO_EXIT, NULL, NULL) !=3D 0) + return 2; + + elf_version (EV_CURRENT); + + switch (command) + { + case classify_file: + open_file (); + return 0; + case classify_elf: + open_elf (); + return 0; + + case classify_executable: + open_elf (); + run_classify (); + if (is_executable ()) + { + if (verbose) + fputs ("info: executable\n", stderr); + return 0; + } + else + { + if (verbose) + fputs ("info: not an executable\n", stderr); + return 1; + } + + case classify_shared: + open_elf (); + run_classify (); + if (is_shared ()) + { + if (verbose) + fputs ("info: shared object\n", stderr); + return 0; + } + else + { + if (verbose) + fputs ("info: not a shared object\n", stderr); + return 1; + } + + case classify_loadable: + open_elf (); + run_classify (); + if (is_loadable ()) + { + if (verbose) + fputs ("info: loadable object\n", stderr); + return 0; + } + else + { + if (verbose) + fputs ("info: not a loadable object\n", stderr); + return 1; + } + } + + return 2; +}