blob: 16ccb22d4786b2f374628175e3cf80b37a47a2f8 [file] [log] [blame]
Yifan Hong770ab052018-11-02 13:43:30 -07001#!/usr/bin/env python
2#
3# Copyright (C) 2018 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17"""
18A tool to extract kernel information from a kernel image.
19"""
20
21import argparse
22import subprocess
23import sys
24import re
25
26CONFIG_PREFIX = b'IKCFG_ST'
27GZIP_HEADER = b'\037\213\010'
28COMPRESSION_ALGO = (
29 (["gzip", "-d"], GZIP_HEADER),
30 (["xz", "-d"], b'\3757zXZ\000'),
31 (["bzip2", "-d"], b'BZh'),
32 (["lz4", "-d", "-l"], b'\002\041\114\030'),
33
34 # These are not supported in the build system yet.
35 # (["unlzma"], b'\135\0\0\0'),
36 # (["lzop", "-d"], b'\211\114\132'),
37)
38
39# "Linux version " UTS_RELEASE " (" LINUX_COMPILE_BY "@"
40# LINUX_COMPILE_HOST ") (" LINUX_COMPILER ") " UTS_VERSION "\n";
41LINUX_BANNER_PREFIX = b'Linux version '
42LINUX_BANNER_REGEX = LINUX_BANNER_PREFIX + \
43 r'([0-9]+[.][0-9]+[.][0-9]+).* \(.*@.*\) \(.*\) .*\n'
44
45
46def get_version(input_bytes, start_idx):
47 null_idx = input_bytes.find('\x00', start_idx)
48 if null_idx < 0:
49 return None
50 linux_banner = input_bytes[start_idx:null_idx].decode()
51 mo = re.match(LINUX_BANNER_REGEX, linux_banner)
52 if mo:
53 return mo.group(1)
54 return None
55
56
57def dump_version(input_bytes):
58 idx = 0
59 while True:
60 idx = input_bytes.find(LINUX_BANNER_PREFIX, idx)
61 if idx < 0:
62 return None
63
64 version = get_version(input_bytes, idx)
65 if version:
66 return version
67
68 idx += len(LINUX_BANNER_PREFIX)
69
70
71def dump_configs(input_bytes):
72 """
73 Dump kernel configuration from input_bytes. This can be done when
74 CONFIG_IKCONFIG is enabled, which is a requirement on Treble devices.
75
76 The kernel configuration is archived in GZip format right after the magic
77 string 'IKCFG_ST' in the built kernel.
78 """
79
80 # Search for magic string + GZip header
81 idx = input_bytes.find(CONFIG_PREFIX + GZIP_HEADER)
82 if idx < 0:
83 return None
84
85 # Seek to the start of the archive
86 idx += len(CONFIG_PREFIX)
87
88 sp = subprocess.Popen(["gzip", "-d", "-c"], stdin=subprocess.PIPE,
89 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
90 o, _ = sp.communicate(input=input_bytes[idx:])
91 if sp.returncode == 1: # error
92 return None
93
94 # success or trailing garbage warning
95 assert sp.returncode in (0, 2), sp.returncode
96
97 return o
98
99
100def try_decompress(cmd, search_bytes, input_bytes):
101 idx = input_bytes.find(search_bytes)
102 if idx < 0:
103 return None
104
105 idx = 0
106 sp = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
107 stderr=subprocess.PIPE)
108 o, _ = sp.communicate(input=input_bytes[idx:])
109 # ignore errors
110 return o
111
112
113def decompress_dump(func, input_bytes):
114 """
115 Run func(input_bytes) first; and if that fails (returns value evaluates to
116 False), then try different decompression algorithm before running func.
117 """
118 o = func(input_bytes)
119 if o:
120 return o
121 for cmd, search_bytes in COMPRESSION_ALGO:
122 decompressed = try_decompress(cmd, search_bytes, input_bytes)
123 if decompressed:
124 o = func(decompressed)
125 if o:
126 return o
127 # Force decompress the whole file even if header doesn't match
128 decompressed = try_decompress(cmd, b"", input_bytes)
129 if decompressed:
130 o = func(decompressed)
131 if o:
132 return o
133
134def main():
135 parser = argparse.ArgumentParser(
136 formatter_class=argparse.RawTextHelpFormatter,
137 description=__doc__ +
138 "\nThese algorithms are tried when decompressing the image:\n " +
139 " ".join(tup[0][0] for tup in COMPRESSION_ALGO))
140 parser.add_argument('--input',
141 help='Input kernel image. If not specified, use stdin',
142 metavar='FILE',
143 type=argparse.FileType('rb'),
144 default=sys.stdin)
145 parser.add_argument('--output-configs',
146 help='If specified, write configs. Use stdout if no file '
147 'is specified.',
148 metavar='FILE',
149 nargs='?',
150 type=argparse.FileType('wb'),
151 const=sys.stdout)
152 parser.add_argument('--output-version',
153 help='If specified, write version. Use stdout if no file '
154 'is specified.',
155 metavar='FILE',
156 nargs='?',
157 type=argparse.FileType('wb'),
158 const=sys.stdout)
159 parser.add_argument('--tools',
160 help='Decompression tools to use. If not specified, PATH '
161 'is searched.',
162 metavar='ALGORITHM:EXECUTABLE',
163 nargs='*')
164 args = parser.parse_args()
165
166 tools = {pair[0]: pair[1]
167 for pair in (token.split(':') for token in args.tools or [])}
168 for cmd, _ in COMPRESSION_ALGO:
169 if cmd[0] in tools:
170 cmd[0] = tools[cmd[0]]
171
172 input_bytes = args.input.read()
173
174 ret = 0
175 if args.output_configs is not None:
176 o = decompress_dump(dump_configs, input_bytes)
177 if o:
178 args.output_configs.write(o)
179 else:
180 sys.stderr.write(
181 "Cannot extract kernel configs in {}".format(args.input.name))
182 ret = 1
183 if args.output_version is not None:
184 o = decompress_dump(dump_version, input_bytes)
185 if o:
186 args.output_version.write(o)
187 else:
188 sys.stderr.write(
189 "Cannot extract kernel versions in {}".format(args.input.name))
190 ret = 1
191
192 return ret
193
194
195if __name__ == '__main__':
196 exit(main())