Skip to content
Snippets Groups Projects
dbgsym-use-build-id 4.23 KiB
Newer Older
#!/usr/bin/env python3

# Copyright 2013-2019 Valve Corporation
# Copyright 2019 Collabora Ltd.
#
# SPDX-License-Identifier: MIT
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

import argparse
import logging
import os
import re
import subprocess
import sys


"""
Provide build-ID-based hard links to legacy path-based debugging symbols.

Based on build-runtime.py in steam-runtime.
"""


logger = logging.getLogger('flatdeb.dbgsym-use-build-id')


def main():
    # type: (...) -> None

    parser = argparse.ArgumentParser(
        description=(
            'Provide build-ID-based hard links for legacy debug symbols'
        ),
    )
    parser.add_argument(
        '--dry-run',
        action='store_true',
        default=False,
    )
    parser.add_argument('--debug-dir', default='/usr/lib/debug')

    args = parser.parse_args()

    for dirname, subdirs, files in os.walk(args.debug_dir):
        # Skip build-ID directory: it is already in the form we wanted
        if '.build-id' in subdirs:
            subdirs.remove('.build-id')

        for f in files:
            # Scrape the output of readelf to find the build-ID for this
            # binary
            path = os.path.join(dirname, f)

            with subprocess.Popen([
                os.environ.get('READELF', 'readelf'), '-n', path,
            ], stdout=subprocess.PIPE, universal_newlines=True) as p:
                for line in p.stdout:
                    m = re.search(
                        r'Build ID: ([a-fA-F0-9]{2})([a-fA-F0-9]+)',
                        line,
                        re.ASCII,
                    )
                    if m is None:
                        continue

                    # ensure no path traversal
                    assert '.' not in m.group(1)
                    assert '.' not in m.group(2)
                    linkdir = os.path.join(
                        args.debug_dir, '.build-id', m.group(1),
                    )
                    link = os.path.join(linkdir, m.group(2) + '.debug')

                    if args.dry_run:
                        logger.info('Would hard-link %s as %s', path, link)
                    else:
                        logger.info('Hard-linking %s as %s', path, link)

                        if not os.access(linkdir, os.W_OK):
                            os.makedirs(linkdir)

                        if os.path.lexists(link):
                            os.unlink(link)

                        os.link(os.path.join(dirname, f), link)

                    break
                else:
                    logger.warning(
                        'Unable to create build-ID-based hard link to %s',
                        path,
                    )


if __name__ == '__main__':
    if sys.stderr.isatty():
        try:
            import colorlog
        except ImportError:
            pass
        else:
            formatter = colorlog.ColoredFormatter(
                '%(log_color)s%(levelname)s:%(name)s:%(reset)s %(message)s')
            handler = logging.StreamHandler()
            handler.setFormatter(formatter)
            logging.getLogger().addHandler(handler)
    else:
        logging.basicConfig()

    logging.getLogger().setLevel(logging.DEBUG)

    try:
        main()
    except KeyboardInterrupt:
        raise SystemExit(130)