File: elfwriter.py

package info (click to toggle)
drgn 0.0.33-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 6,892 kB
  • sloc: python: 59,081; ansic: 51,400; awk: 423; makefile: 339; sh: 113
file content (346 lines) | stat: -rw-r--r-- 11,437 bytes parent folder | download
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
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
# Copyright (c) Meta Platforms, Inc. and affiliates.
# SPDX-License-Identifier: LGPL-2.1-or-later

import os
import struct
from typing import List, NamedTuple, Optional, Sequence, Tuple, Union
import zlib

from _drgn_util.elf import ET, PT, SHF, SHN, SHT, STB, STT, STV


class ElfSection:
    def __init__(
        self,
        *,
        data: bytes = b"",
        name: Optional[str] = None,
        sh_type: Optional[SHT] = None,
        p_type: Optional[PT] = None,
        vaddr: int = 0,
        paddr: int = 0,
        memsz: Optional[int] = None,
        p_align: int = 0,
        sh_link: int = 0,
        sh_info: int = 0,
        sh_entsize: int = 0,
        sh_flags: SHF = SHF(0),
    ):
        self.data = data
        self.name = name
        self.sh_type = sh_type
        self.sh_flags = sh_flags
        self.p_type = p_type
        self.vaddr = vaddr
        self.paddr = paddr
        self.memsz = memsz
        self.p_align = p_align
        self.sh_link = sh_link
        self.sh_info = sh_info
        self.sh_entsize = sh_entsize

        assert (self.name is not None) or (self.p_type is not None)
        assert (self.name is None) == (self.sh_type is None)
        assert self.p_type is None or not (sh_flags & SHF.COMPRESSED)


class ElfSymbol(NamedTuple):
    name: str
    value: int
    size: int
    type: STT
    binding: STB
    shindex: Optional[int] = None
    visibility: STV = STV.DEFAULT

    def st_info(self) -> int:
        return (self.binding << 4) + (self.type & 0xF)


def _create_symtab(
    sections: List[ElfSection],
    symbols: Sequence[ElfSymbol],
    *,
    dynamic: bool = False,
    little_endian: bool,
    bits: int,
):
    symtab_name = ".dynsym" if dynamic else ".symtab"
    strtab_name = ".dynstr" if dynamic else ".strtab"
    assert not any(section.name in (symtab_name, strtab_name) for section in sections)

    # An empty symbol name is a placeholder for the implicit 0-index entry in
    # the symbol table. It's used to create a valid, but empty symbol table.
    if symbols and symbols[0].name == "":
        symbols = symbols[1:]

    endian = "<" if little_endian else ">"
    if bits == 64:
        symbol_struct = struct.Struct(endian + "IBBHQQ")

        def symbol_fields(sym: ElfSymbol):
            return (
                sym.st_info(),
                sym.visibility,
                SHN.UNDEF if sym.shindex is None else sym.shindex,
                sym.value,
                sym.size,
            )

    else:
        symbol_struct = struct.Struct(endian + "IIIBBH")

        def symbol_fields(sym: ElfSymbol):
            return (
                sym.value,
                sym.size,
                sym.st_info(),
                sym.visibility,
                SHN.UNDEF if sym.shindex is None else sym.shindex,
            )

    symtab_data = bytearray((len(symbols) + 1) * symbol_struct.size)
    strtab_data = bytearray(1)
    sh_info = 1
    for i, sym in enumerate(symbols, 1):
        symbol_struct.pack_into(
            symtab_data, i * symbol_struct.size, len(strtab_data), *symbol_fields(sym)
        )
        strtab_data.extend(sym.name.encode())
        strtab_data.append(0)
        if sym.binding == STB.LOCAL:
            assert sh_info == i, "local symbol after non-local symbol"
            sh_info = i + 1

    sections.append(
        ElfSection(
            name=symtab_name,
            sh_type=SHT.DYNSYM if dynamic else SHT.SYMTAB,
            data=symtab_data,
            sh_link=sum((1 for section in sections if section.name is not None), 2),
            sh_info=sh_info,
            sh_entsize=symbol_struct.size,
        )
    )
    sections.append(ElfSection(name=strtab_name, sh_type=SHT.STRTAB, data=strtab_data))


def create_elf_file(
    type: ET,
    sections: Sequence[ElfSection] = (),
    symbols: Sequence[ElfSymbol] = (),
    *,
    dynamic_symbols: Sequence[ElfSymbol] = (),
    build_id: Optional[bytes] = None,
    gnu_debuglink: Optional[
        Tuple[Union[str, bytes, "os.PathLike[str]", "os.PathLike[bytes]"], int]
    ] = None,
    gnu_debugaltlink: Optional[
        Tuple[Union[str, bytes, "os.PathLike[str]", "os.PathLike[bytes]"], bytes]
    ] = None,
    little_endian: bool = True,
    bits: int = 64,
):
    endian = "<" if little_endian else ">"
    if bits == 64:
        ehdr_struct = struct.Struct(endian + "16BHHIQQQIHHHHHH")
        shdr_struct = struct.Struct(endian + "IIQQQQIIQQ")
        phdr_struct = struct.Struct(endian + "IIQQQQQQ")
        chdr_struct = struct.Struct(endian + "IIQQ")
        e_machine = 62 if little_endian else 43  # EM_X86_64 or EM_SPARCV9
    else:
        assert bits == 32
        ehdr_struct = struct.Struct(endian + "16BHHIIIIIHHHHHH")
        shdr_struct = struct.Struct(endian + "10I")
        phdr_struct = struct.Struct(endian + "8I")
        chdr_struct = struct.Struct(endian + "III")
        e_machine = 3 if little_endian else 8  # EM_386 or EM_MIPS
    nhdr_struct = struct.Struct(endian + "3I")

    sections = list(sections)
    if dynamic_symbols:
        _create_symtab(
            sections,
            dynamic_symbols,
            dynamic=True,
            little_endian=little_endian,
            bits=bits,
        )
    if symbols:
        _create_symtab(sections, symbols, little_endian=little_endian, bits=bits)
    if build_id is not None:
        build_id_note = (
            nhdr_struct.pack(
                4,  # n_namesz,
                len(build_id),  # n_namesz,
                3,  # n_type = NT_GNU_BUILD_ID
            )
            + b"GNU\0"
            + build_id
            + bytes(-len(build_id) % 4)
        )
        sections.append(
            ElfSection(name=".note.gnu.build-id", sh_type=SHT.NOTE, data=build_id_note)
        )

    if gnu_debuglink is not None:
        gnu_debuglink_path, gnu_debuglink_crc = gnu_debuglink
        gnu_debuglink_path = os.fsencode(gnu_debuglink_path)
        sections.append(
            ElfSection(
                name=".gnu_debuglink",
                sh_type=SHT.PROGBITS,
                data=gnu_debuglink_path
                + bytes(4 - len(gnu_debuglink_path) % 4)
                + gnu_debuglink_crc.to_bytes(4, "little"),
            )
        )

    if gnu_debugaltlink is not None:
        gnu_debugaltlink_path, gnu_debugaltlink_build_id = gnu_debugaltlink
        sections.append(
            ElfSection(
                name=".gnu_debugaltlink",
                sh_type=SHT.PROGBITS,
                data=os.fsencode(gnu_debugaltlink_path)
                + b"\0"
                + gnu_debugaltlink_build_id,
            )
        )

    shnum = 0
    phnum = 0
    shstrtab = bytearray(1)
    for section in sections:
        if section.name is not None:
            shstrtab.extend(section.name.encode())
            shstrtab.append(0)
            shnum += 1
        if section.p_type is not None:
            phnum += 1
    if shnum > 0:
        shnum += 2  # One for the SHT_NULL section, one for .shstrtab.
        shstrtab.extend(b".shstrtab\0")
        sections.append(ElfSection(name=".shstrtab", sh_type=SHT.STRTAB, data=shstrtab))

    shdr_offset = ehdr_struct.size
    phdr_offset = shdr_offset + shdr_struct.size * shnum
    headers_size = phdr_offset + phdr_struct.size * phnum
    buf = bytearray(headers_size)
    ehdr_struct.pack_into(
        buf,
        0,
        0x7F,  # ELFMAG0
        ord("E"),  # ELFMAG1
        ord("L"),  # ELFMAG2
        ord("F"),  # ELFMAG3
        2 if bits == 64 else 1,  # EI_CLASS = ELFCLASS64 or ELFCLASS32
        1 if little_endian else 2,  # EI_DATA = ELFDATA2LSB or ELFDATA2MSB
        1,  # EI_VERSION = EV_CURRENT
        0,  # EI_OSABI = ELFOSABI_NONE
        0,  # EI_ABIVERSION
        0,
        0,
        0,
        0,
        0,
        0,
        0,  # EI_PAD
        type,  # e_type
        e_machine,
        1,  # e_version = EV_CURRENT
        0,  # e_entry
        phdr_offset if phnum else 0,  # e_phoff
        shdr_offset if shnum else 0,  # e_shoff
        0,  # e_flags
        ehdr_struct.size,  # e_ehsize
        phdr_struct.size,  # e_phentsize
        phnum,  # e_phnum
        shdr_struct.size,  # e_shentsize
        shnum,  # e_shnum
        shnum - 1 if shnum else 0,  # e_shstrndx
    )

    shdr_offset += shdr_struct.size
    for section in sections:
        ch_addralign = 1 if section.p_type is None else bits // 8
        memsz = len(section.data) if section.memsz is None else section.memsz
        if section.sh_flags & SHF.COMPRESSED:
            sh_addralign = bits // 8
            compressed_data = zlib.compress(section.data)
            sh_size = chdr_struct.size + len(compressed_data)
        else:
            sh_addralign = ch_addralign
            sh_size = memsz
        if section.p_align:
            padding = section.vaddr % section.p_align - len(buf) % section.p_align
            buf.extend(bytes(padding))
        if section.name is not None:
            shdr_struct.pack_into(
                buf,
                shdr_offset,
                shstrtab.index(section.name.encode()),  # sh_name
                section.sh_type,  # sh_type
                section.sh_flags,  # sh_flags
                section.vaddr,  # sh_addr
                len(buf),  # sh_offset
                sh_size,  # sh_size
                section.sh_link,  # sh_link
                section.sh_info,  # sh_info
                sh_addralign,  # sh_addralign
                section.sh_entsize,  # sh_entsize
            )
            shdr_offset += shdr_struct.size
        if section.p_type is not None:
            flags = 7  # PF_R | PF_W | PF_X
            if bits == 64:
                phdr_struct.pack_into(
                    buf,
                    phdr_offset,
                    section.p_type,  # p_type
                    flags,  # p_flags
                    len(buf),  # p_offset
                    section.vaddr,  # p_vaddr
                    section.paddr,  # p_paddr
                    len(section.data),  # p_filesz
                    memsz,  # p_memsz
                    section.p_align,  # p_align
                )
            else:
                phdr_struct.pack_into(
                    buf,
                    phdr_offset,
                    section.p_type,  # p_type
                    len(buf),  # p_offset
                    section.vaddr,  # p_vaddr
                    section.paddr,  # p_paddr
                    len(section.data),  # p_filesz
                    memsz,  # p_memsz
                    flags,  # p_flags
                    section.p_align,  # p_align
                )
            phdr_offset += phdr_struct.size
        if section.sh_flags & SHF.COMPRESSED:
            ELFCOMPRESS_ZLIB = 1
            if bits == 64:
                buf.extend(
                    chdr_struct.pack(
                        ELFCOMPRESS_ZLIB,  # ch_type
                        0,  # ch_reserved
                        memsz,  # ch_size
                        ch_addralign,  # ch_addralign
                    )
                )
            else:
                buf.extend(
                    chdr_struct.pack(
                        ELFCOMPRESS_ZLIB,  # ch_type
                        memsz,  # ch_size
                        ch_addralign,  # ch_addralign
                    )
                )
            buf.extend(compressed_data)
        else:
            buf.extend(section.data)

    return buf