forked from firecracker-microvm/firecracker
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtest_bindings.py
165 lines (129 loc) · 5.24 KB
/
test_bindings.py
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
# Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0
"""
Script used to check if bindgen-generated code creates structs that differ from previously created
onces.
The script uses `pahole` (man 1 pahole) to gather debug information from two firecracker binaries
(script's arguments). It parses pahole output and gathers struct information in a dictionary of the
form:
```
{
"struct_name": {"size": size_in_bytes, "alignment": alignment_in_bytes},
...
}
```
It also, filters structure names using the "bindings" filter for keeping only bindgen related
structs.
*NOTE*: this assumes that all bindgen-related structs live under a crate or module name with
"bindings" in it. At the moment, this is true.
It then iterates through the structs of the firecracker binary built from the older version and
checks if there are mismatches with the struct info from the second binary (newer version)
### Usage
1. Create the two binaries
```
# First create the binary with existing bindings
$ git checkout main
$ ./tools/devtool build
$ cp ./build/cargo_target/x86_64-unknown-linux-musl/debug/firecracker firecracker_old
# Second create the binary with new bindings
$ git checkout new_bindings
$ ./tools/devtool build
$ cp ./build/cargo_target/x86_64-unknown-linux-musl/debug/firecracker firecracker_new
# Run the script
$ python3 ./tools/test_bindings.py firecracker_old firecracker_new
```
"""
import argparse
import logging
import re
import subprocess
import sys
logging.basicConfig(level=logging.DEBUG)
log = logging.getLogger(__name__)
def parse_pahole(pahole_output):
"""Gather bindings related structs from pahole output
Parse pahole output and gather struct information filtering for the 'bindings' keyword.
The information gathered is the struct size and its alignment.
@param fname: File including pahole output
@return: A dictionary where keys are struct names and values struct size and alignment
"""
ret = {}
# regular expression matches the name of the struct, its size and alignment
structs = re.findall(
rb"struct (.*?)\{.*?/\* size: (\d+).*?\*/.*?\n\} "
rb"__attribute__\(\(__aligned__\((\d+)\)\)\)\;",
pahole_output,
flags=re.DOTALL,
)
for struct in structs:
struct_name = str(struct[0])
size = int(struct[1])
alignment = int(struct[2])
if "bindings" in struct_name:
ret[struct_name] = {"size": size, "alignment": alignment}
return ret
def pahole(binary: str) -> str:
"""Runs pahole on a binary and returns its output as a str
If pahole fails this will raise a `CalledProcessError`
@param binary: binary to run pahole on
@return: On success, it will return the stdout of the pahole process
"""
result = subprocess.run(
["pahole", binary], stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True
)
return result.stdout
def check_pahole_mismatches(old: str, new: str) -> bool:
"""Checks for pahole mismatches in pahole information between two binaries
@param old: old Firecracker binary
@param new: new Firecracker binary
@return: false if no mismatches found, true otherwise
"""
pahole_structs_1 = parse_pahole(pahole(old))
pahole_structs_2 = parse_pahole(pahole(new))
# We go through all the structs existing in the old firecracker binary and check for mismatches
# in the new one.
for name, prop_1 in pahole_structs_1.items():
# Note that the reverse, i.e. a name existing in the new binary but not in the old binary,
# is not a problem. That would mean we are making use of some new struct from
# bindgen-generated code. That does not break ABI compatibility.
if name not in pahole_structs_2:
log.warning("struct '%s' does not exist in new binary", name)
continue
prop_2 = pahole_structs_2[name]
# Size mismatches are hard errors
if prop_1["size"] != prop_2["size"]:
log.error("size of '%s' does not match in two binaries", name)
log.error("old: %s", prop_1["size"])
log.error("new: %s", prop_2["size"])
return True
# Alignment mismatches just cause warnings
if prop_1["alignment"] != prop_2["alignment"]:
log.warning("alignment of '%s' does not match in two binaries", name)
log.warning("old: %s", prop_1["alignment"])
log.warning("new: %s", prop_2["alignment"])
else:
log.info("struct '%s' matches", name)
return False
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Check bindings ABI compatibility for Firecracker"
)
parser.add_argument(
"firecracker_old",
type=str,
metavar="old-firecracker-binary",
help="Firecracker binary with old bindings",
)
parser.add_argument(
"firecracker_new",
type=str,
metavar="new-firecracker-binary",
help="Firecracker binary with new bindings",
)
args = parser.parse_args()
if check_pahole_mismatches(args.firecracker_old, args.firecracker_new):
log.error("Structure layout mismatch")
sys.exit(1)
else:
log.info("Structure layout matches")
sys.exit(0)