about summary refs log tree commit diff
path: root/elf/tst-glibcelf.py
blob: c191636a997c8e34535163cc53838c8b7631aada (plain) (blame)
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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
#!/usr/bin/python3
# Verify scripts/glibcelf.py contents against elf/elf.h.
# Copyright (C) 2022-2024 Free Software Foundation, Inc.
# This file is part of the GNU C Library.
#
# The GNU C Library 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.
#
# The GNU C 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 the GNU C Library; if not, see
# <https://www.gnu.org/licenses/>.

import argparse
import sys

import glibcelf
import glibcextract

errors_encountered = 0

def error(message):
    global errors_encountered
    sys.stdout.write('error: {}\n'.format(message))
    errors_encountered += 1

# The enum constants in glibcelf are expected to have exactly these
# prefixes.
expected_constant_prefixes = tuple(
    'ELFCLASS ELFDATA EM_ ET_ DT_ PF_ PT_ SHF_ SHN_ SHT_ STB_ STT_'.split())

def find_constant_prefix(name):
    """Returns a matching prefix from expected_constant_prefixes or None."""
    for prefix in expected_constant_prefixes:
        if name.startswith(prefix):
            return prefix
    return None

def find_enum_types():
    """A generator for OpenIntEnum and IntFlag classes in glibcelf."""
    classes = set((glibcelf._TypedConstant, glibcelf._IntConstant,
                   glibcelf._FlagConstant))
    for obj in vars(glibcelf).values():
        if isinstance(obj, type) and obj not in classes \
           and obj.__bases__[0] in classes:
            yield obj

def check_basic():
    """Check basic functionality of the constant classes."""

    if glibcelf.Pt.PT_NULL is not glibcelf.Pt(0):
        error('Pt(0) not interned')
    if glibcelf.Pt(17609) is glibcelf.Pt(17609):
        error('Pt(17609) unexpectedly interned')
    if glibcelf.Pt(17609) == glibcelf.Pt(17609):
        pass
    else:
        error('Pt(17609) equality')
    if glibcelf.Pt(17610) == glibcelf.Pt(17609):
        error('Pt(17610) equality')

    if str(glibcelf.Pt.PT_NULL) != 'PT_NULL':
        error('str(PT_NULL)')
    if str(glibcelf.Pt(17609)) != '17609':
        error('str(Pt(17609))')

    if repr(glibcelf.Pt.PT_NULL) != 'PT_NULL':
        error('repr(PT_NULL)')
    if repr(glibcelf.Pt(17609)) != 'Pt(17609)':
        error('repr(Pt(17609))')

    if glibcelf.Pt('PT_AARCH64_MEMTAG_MTE') \
       is not glibcelf.Pt.PT_AARCH64_MEMTAG_MTE:
        error('PT_AARCH64_MEMTAG_MTE identity')
    if glibcelf.Pt(0x70000002) is glibcelf.Pt.PT_AARCH64_MEMTAG_MTE:
        error('Pt(0x70000002) identity')
    if glibcelf.PtAARCH64(0x70000002) is not glibcelf.Pt.PT_AARCH64_MEMTAG_MTE:
        error('PtAARCH64(0x70000002) identity')
    if glibcelf.Pt.PT_AARCH64_MEMTAG_MTE.short_name != 'AARCH64_MEMTAG_MTE':
        error('PT_AARCH64_MEMTAG_MTE short name')

    # Special cases for int-like Shn.
    if glibcelf.Shn(32) == glibcelf.Shn.SHN_XINDEX:
        error('Shn(32)')
    if glibcelf.Shn(32) + 0 != 32:
        error('Shn(32) + 0')
    if 32 in glibcelf.Shn:
        error('32 in Shn')
    if 0 not in glibcelf.Shn:
        error('0 not in Shn')

def check_duplicates():
    """Verifies that enum types do not have duplicate values.

    Different types must have different member names, too.

    """
    global_seen = {}
    for typ in find_enum_types():
        seen = {}
        for (name, e) in typ.by_name.items():
            if e.value in seen:
                other = seen[e.value]
                # Value conflicts only count if they are between
                # the same base type.
                if e.__class__ is typ and other.__class__ is typ:
                    error('{} has {}={} and {}={}'.format(
                        typ, other, e.value, name, e.value))
            else:
                seen[e.value] = name
                if name in global_seen:
                    error('{} used in {} and {}'.format(
                        name, global_seen[name], typ))
                else:
                    global_seen[name] = typ

def check_constant_prefixes():
    """Check that the constant prefixes match expected_constant_prefixes."""
    seen = set()
    for typ in find_enum_types():
        typ_prefix = None
        for val in typ.by_name.values():
            prefix = find_constant_prefix(val.name)
            if prefix is None:
                error('constant {!r} for {} has unknown prefix'.format(
                    val, typ))
                break
            elif typ_prefix is None:
                typ_prefix = prefix
                seen.add(typ_prefix)
            elif prefix != typ_prefix:
                error('prefix {!r} for constant {!r}, expected {!r}'.format(
                    prefix, val, typ_prefix))
        if typ_prefix is None:
            error('empty enum type {}'.format(typ))

    for prefix in sorted(set(expected_constant_prefixes) - seen):
        error('missing constant prefix {!r}'.format(prefix))
    # Reverse difference is already covered inside the loop.

def find_elf_h_constants(cc):
    """Returns a dictionary of relevant constants from <elf.h>."""
    return glibcextract.compute_macro_consts(
        source_text='#include <elf.h>',
        cc=cc,
        macro_re='|'.join(
            prefix + '.*' for prefix in expected_constant_prefixes))

# The first part of the pair is a name of an <elf.h> constant that is
# dropped from glibcelf.  The second part is the constant as it is
# used in <elf.h>.
glibcelf_skipped_aliases = (
    ('EM_ARC_A5', 'EM_ARC_COMPACT'),
)

# Constants that provide little value and are not included in
# glibcelf: *LO*/*HI* range constants, *NUM constants counting the
# number of constants.  Also includes the alias names from
# glibcelf_skipped_aliases.
glibcelf_skipped_constants = frozenset(
    [e[0] for e in glibcelf_skipped_aliases]) | frozenset("""
DT_AARCH64_NUM
DT_ADDRNUM
DT_ADDRRNGHI
DT_ADDRRNGLO
DT_ALPHA_NUM
DT_ENCODING
DT_EXTRANUM
DT_HIOS
DT_HIPROC
DT_IA_64_NUM
DT_LOOS
DT_LOPROC
DT_MIPS_NUM
DT_NUM
DT_PPC64_NUM
DT_PPC_NUM
DT_PROCNUM
DT_SPARC_NUM
DT_VALNUM
DT_VALRNGHI
DT_VALRNGLO
DT_VERSIONTAGNUM
DT_X86_64_NUM
ELFCLASSNUM
ELFDATANUM
EM_NUM
ET_HIOS
ET_HIPROC
ET_LOOS
ET_LOPROC
ET_NUM
PF_MASKOS
PF_MASKPROC
PT_HIOS
PT_HIPROC
PT_HISUNW
PT_LOOS
PT_LOPROC
PT_LOSUNW
PT_NUM
SHF_MASKOS
SHF_MASKPROC
SHN_HIOS
SHN_HIPROC
SHN_HIRESERVE
SHN_LOOS
SHN_LOPROC
SHN_LORESERVE
SHT_HIOS
SHT_HIPROC
SHT_HIPROC
SHT_HISUNW
SHT_HIUSER
SHT_LOOS
SHT_LOPROC
SHT_LOSUNW
SHT_LOUSER
SHT_NUM
STB_HIOS
STB_HIPROC
STB_LOOS
STB_LOPROC
STB_NUM
STT_HIOS
STT_HIPROC
STT_LOOS
STT_LOPROC
STT_NUM
""".strip().split())

def check_constant_values(cc):
    """Checks the values of <elf.h> constants against glibcelf."""

    glibcelf_constants = {
        e.name: e for typ in find_enum_types() for e in typ.by_name.values()}
    elf_h_constants = find_elf_h_constants(cc=cc)

    missing_in_glibcelf = (set(elf_h_constants) - set(glibcelf_constants)
                           - glibcelf_skipped_constants)
    for name in sorted(missing_in_glibcelf):
        error('constant {} is missing from glibcelf'.format(name))

    unexpected_in_glibcelf = \
        set(glibcelf_constants) & glibcelf_skipped_constants
    for name in sorted(unexpected_in_glibcelf):
        error('constant {} is supposed to be filtered from glibcelf'.format(
            name))

    missing_in_elf_h = set(glibcelf_constants) - set(elf_h_constants)
    for name in sorted(missing_in_elf_h):
        error('constant {} is missing from <elf.h>'.format(name))

    expected_in_elf_h = glibcelf_skipped_constants - set(elf_h_constants)
    for name in expected_in_elf_h:
        error('filtered constant {} is missing from <elf.h>'.format(name))

    for alias_name, name_in_glibcelf in glibcelf_skipped_aliases:
        if name_in_glibcelf not in glibcelf_constants:
            error('alias value {} for {} not in glibcelf'.format(
                name_in_glibcelf, alias_name))
        elif (int(elf_h_constants[alias_name])
              != glibcelf_constants[name_in_glibcelf].value):
            error('<elf.h> has {}={}, glibcelf has {}={}'.format(
                alias_name, elf_h_constants[alias_name],
                name_in_glibcelf, glibcelf_constants[name_in_glibcelf]))

    # Check for value mismatches:
    for name in sorted(set(glibcelf_constants) & set(elf_h_constants)):
        glibcelf_value = glibcelf_constants[name].value
        elf_h_value = int(elf_h_constants[name])
        # On 32-bit architectures <elf.h> has some constants that are
        # parsed as signed, while they are unsigned in glibcelf.  So
        # far, this only affects some flag constants, so special-case
        # them here.
        if (glibcelf_value != elf_h_value
            and not (isinstance(glibcelf_constants[name],
                                glibcelf._FlagConstant)
                     and glibcelf_value == 1 << 31
                     and elf_h_value == -(1 << 31))):
            error('{}: glibcelf has {!r}, <elf.h> has {!r}'.format(
                name, glibcelf_value, elf_h_value))

def check_hashes():
    for name, expected_elf, expected_gnu in (
            ('', 0, 0x1505),
            ('PPPPPPPPPPPP', 0, 0x9f105c45),
            ('GLIBC_2.0', 0xd696910, 0xf66c3dd5),
            ('GLIBC_2.34', 0x69691b4, 0xc3f3f90c),
            ('GLIBC_PRIVATE', 0x963cf85, 0x692a260)):
        for convert in (lambda x: x, lambda x: x.encode('UTF-8')):
            name = convert(name)
            actual_elf = glibcelf.elf_hash(name)
            if actual_elf != expected_elf:
                error('elf_hash({!r}): {:x} != 0x{:x}'.format(
                    name, actual_elf, expected_elf))
            actual_gnu = glibcelf.gnu_hash(name)
            if actual_gnu != expected_gnu:
                error('gnu_hash({!r}): {:x} != 0x{:x}'.format(
                    name, actual_gnu, expected_gnu))

def main():
    """The main entry point."""
    parser = argparse.ArgumentParser(
        description="Check glibcelf.py and elf.h against each other.")
    parser.add_argument('--cc', metavar='CC',
                        help='C compiler (including options) to use')
    args = parser.parse_args()

    check_basic()
    check_duplicates()
    check_constant_prefixes()
    check_constant_values(cc=args.cc)
    check_hashes()

    if errors_encountered > 0:
        print("note: errors encountered:", errors_encountered)
        sys.exit(1)

if __name__ == '__main__':
    main()