summaryrefslogtreecommitdiff
path: root/abi/abitool.py
blob: acd999ed832b06e6422cb4e6bd9ba13155adbe6f (plain)
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
#!/usr/bin/env python3
#
# Copyright (C) 2019 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#       http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

import re
import subprocess
import logging

log = logging.getLogger(__name__)

class AbiTool(object):
    """ Base class for different kinds of abi analysis tools"""
    def dump_kernel_abi(self, linux_tree, dump_path, whitelist):
        raise NotImplementedError()

    def diff_abi(self, old_dump, new_dump, diff_report, short_report, whitelist):
        raise NotImplementedError()

    def name(self):
        raise NotImplementedError()

ABIDIFF_ERROR                   = (1<<0)
ABIDIFF_USAGE_ERROR             = (1<<1)
ABIDIFF_ABI_CHANGE              = (1<<2)
ABIDIFF_ABI_INCOMPATIBLE_CHANGE = (1<<3)

class Libabigail(AbiTool):
    """" Concrete AbiTool implementation for libabigail """
    def dump_kernel_abi(self, linux_tree, dump_path, whitelist):
        dump_abi_cmd = ['abidw',
                        # omit various sources of indeterministic abidw output
                        '--no-corpus-path',
                        '--no-comp-dir-path',
                        # the path containing vmlinux and *.ko
                        '--linux-tree',
                        linux_tree,
                        '--out-file',
                        dump_path]

        if whitelist is not None:
            dump_abi_cmd.extend(['--kmi-whitelist', whitelist])

        subprocess.check_call(dump_abi_cmd)

    def diff_abi(self, old_dump, new_dump, diff_report, short_report, whitelist):
        log.info('libabigail diffing: {} and {} at {}'.format(old_dump,
                                                                new_dump,
                                                                diff_report))
        diff_abi_cmd = ['abidiff',
                        '--leaf-changes-only',
                        '--flag-indirect',
                        '--impacted-interfaces',
                        '--dump-diff-tree',
                        old_dump,
                        new_dump]

        if whitelist is not None:
            diff_abi_cmd.extend(['--kmi-whitelist', whitelist])

        abi_changed = False

        with open(diff_report, 'w') as out:
            try:
                subprocess.check_call(diff_abi_cmd, stdout=out, stderr=out)
            except subprocess.CalledProcessError as e:
                if e.returncode & (ABIDIFF_ERROR | ABIDIFF_USAGE_ERROR):
                    raise
                abi_changed = True  # actual abi change

        if short_report is not None:
            with open(diff_report) as full_report:
                with open(short_report, 'w') as out:
                    out.write(re.sub(
                        r"^( *)([^ ]* impacted interfaces?):\n(?:^\1 .*\n)*",
                        r"\1\2\n",
                        full_report.read(),
                        flags=re.MULTILINE))

        return abi_changed

def get_abi_tool(abi_tool):
    if abi_tool == 'libabigail':
        log.info('using libabigail for abi analysis')
        return Libabigail()

    raise ValueError("not a valid abi_tool: %s" % abi_tool)