1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125
|
// Copyright (c) Meta Platforms, Inc. and affiliates.
// SPDX-License-Identifier: LGPL-2.1-or-later
/**
* @file
*
* ELF note parsing.
*/
#ifndef DRGN_ELF_NOTES_H
#define DRGN_ELF_NOTES_H
#include <elfutils/version.h>
#include <gelf.h>
#include <stdbool.h>
/**
* Parse the next ELF note out of a buffer.
*
* @note
* Alignment of ELF notes is a mess. The [System V
* gABI](http://www.sco.com/developers/gabi/latest/ch5.pheader.html#note_section)
* says that the note header and descriptor should be aligned to 4 bytes for
* 32-bit files and 8 bytes for 64-bit files. However, on Linux, 4-byte
* alignment is used for both 32-bit and 64-bit files.
* @note
* The only exception as of 2024 is `NT_GNU_PROPERTY_TYPE_0`, which is defined
* to follow the gABI alignment. See
* ["PT_NOTE alignment, NT_GNU_PROPERTY_TYPE_0, glibc and gold"](https://public-inbox.org/libc-alpha/13a92cb0-a993-f684-9a96-e02e4afb1bef@redhat.com/).
* But, note that the 12-byte note header plus the 4-byte `"GNU\0"` name is a
* multiple of 8 bytes, and the `NT_GNU_PROPERTY_TYPE_0` descriptor is defined
* to be a multiple of 4 bytes for 32-bit files and 8 bytes for 64-bit files. As
* a result, `NT_GNU_PROPERTY_TYPE_0` is never actually padded, and 4-byte vs.
* 8-byte alignment are equivalent for parsing purposes.
* @note
* According to the [gABI Linux
* Extensions](https://gitlab.com/x86-psABIs/Linux-ABI), consumers are now
* supposed to use the `p_align` of the `PT_NOTE` segment instead of assuming an
* alignment. However, the Linux kernel as of 6.0 generates core dumps with
* `PT_NOTE` segments with a `p_align` of 0 or 1 which are actually aligned to 4
* bytes. So, when parsing notes from an ELF file, you need to use 8-byte
* alignment if `p_align` is 8 and 4-byte alignment otherwise. binutils and
* elfutils appear to do the same.
* @note
* Before Linux kernel commit f7ba52f302fd ("vmlinux.lds.h: Discard
* .note.gnu.property section") (in v6.4), the vmlinux linker script can create
* a `PT_NOTE` segment with a `p_align` of 8 where the entries other than
* `NT_GNU_PROPERTY_TYPE_0` are actually aligned to 4 bytes.
* @note
* Finally, there are cases where we don't know `p_align`. For example,
* `/sys/kernel/notes` contains the contents of the vmlinux `.notes` section,
* which (ignoring the aforementioned bug) we can assume has 4-byte alignment.
* As another example, `/sys/module/$module/notes/` contains a file for each
* note section. Since `NT_GNU_PROPERTY_TYPE_0` can be parsed assuming 4-byte
* alignment, we can again assume 4-byte alignment. This will work as long as
* any future note types requiring 8-byte alignment also happen to have an
* 8-byte aligned header+name and descriptor (but hopefully no one ever adds an
* 8-byte aligned note again).
*
* @param[in,out] p Current position. Initialize to the beginning of the buffer.
* @param[in,out] size Remaining size. Initialize to the size of the buffer.
* @param[in] align Note alignment. Usually `p_align == 8 ? 8 : 4` if the
* program header is available, otherwise 4.
* @param[in] bswap Whether the note header needs to be byte-swapped.
* @param[out] name_ret Returned note name.
* @param[out] desc_ret Returned note descriptor.
* @return @c true if a note was parsed, @c false if there are no more notes.
*/
bool next_elf_note(const void **p, size_t *size, unsigned int align, bool bswap,
GElf_Nhdr *nhdr_ret, const char **name_ret,
const void **desc_ret);
/**
* Find an ELF note matching the given name and type.
*
* Note that this currently only checks segments, not sections.
*
* @return 0 on success, -1 on libelf error.
*/
int find_elf_note(Elf *elf, const char *name, uint32_t type, const void **ret,
size_t *size_ret);
/**
* Parse a GNU build ID from a buffer containing note data.
*
* @param[in] buf Buffer containing note data.
* @param[in] size Size of @p buf in bytes.
* @param[in] align Note alignment. See @ref next_elf_note().
* @param[in] bswap Whether the note header needs to be byte-swapped.
* @param[out] ret Returned build ID, or @c NULL if not found.
* @return Size of returned build ID in bytes, or @c NULL if not found.
*/
size_t parse_gnu_build_id_from_notes(const void *buf, size_t size,
unsigned int align, bool bswap,
const void **ret);
#if _ELFUTILS_PREREQ(0, 175)
/**
* Find an ELF file's GNU build ID, working around `vmlinux` files with broken
* note alignment when possible.
*
* @param[out] ret Returned build ID, or @c NULL if not found.
* @return Size of returned build ID in bytes, or @c NULL if not found.
*/
ssize_t drgn_elf_gnu_build_id(Elf *elf, const void **ret);
#else
#include <elfutils/libdwelf.h>
static inline ssize_t drgn_elf_gnu_build_id(Elf *elf, const void **ret)
{
return dwelf_elf_gnu_build_id(elf, ret);
}
#endif
static inline Elf_Type note_header_type(uint64_t p_align)
{
#if _ELFUTILS_PREREQ(0, 175)
if (p_align == 8)
return ELF_T_NHDR8;
#endif
return ELF_T_NHDR;
}
#endif /* DRGN_ELF_NOTES_H */
|