You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

340 lines
12 KiB

# this file contains definitions related to the Linux kernel itself
#
# list here the macros that you know are always defined/undefined when including
# the kernel headers
#
import sys, cpp, re, os.path, time
from defaults import *
verboseSearch = 0
verboseFind = 0
########################################################################
########################################################################
##### #####
##### H E A D E R S C A N N E R #####
##### #####
########################################################################
########################################################################
class HeaderScanner:
"""a class used to non-recursively detect which Linux kernel headers are
used by a given set of input source files"""
# to use the HeaderScanner, do the following:
#
# scanner = HeaderScanner()
# for path in <your list of files>:
# scanner.parseFile(path)
#
# # get the set of Linux headers included by your files
# headers = scanner.getHeaders()
#
# # get the set of of input files that do include Linux headers
# files = scanner.getFiles()
#
# note that the result of getHeaders() is a set of strings, each one
# corresponding to a non-bracketed path name, e.g.:
#
# set("linux/types","asm/types.h")
#
# the default algorithm is pretty smart and will analyze the input
# files with a custom C pre-processor in order to optimize out macros,
# get rid of comments, empty lines, etc..
#
# this avoids many annoying false positives... !!
#
# this regular expression is used to detect include paths that relate to
# the kernel, by default, it selects one of:
# <linux/*>
# <asm/*>
# <asm-generic/*>
# <mtd/*>
#
re_combined_str=\
r"^.*<((%s)/[\d\w_\+\.\-/]*)>.*$" % "|".join(kernel_dirs)
re_combined = re.compile(re_combined_str)
# some kernel files choose to include files with relative paths (x86 32/64
# dispatch for instance)
re_rel_dir = re.compile(r'^.*"([\d\w_\+\.\-/]+)".*$')
def __init__(self,config={}):
"""initialize a HeaderScanner"""
self.reset()
self.config = config
def reset(self,config={}):
self.files = set() # set of files being parsed for headers
self.headers = {} # maps headers to set of users
self.config = config
def checkInclude(self, line, from_file, kernel_root=None):
relative = False
m = HeaderScanner.re_combined.match(line)
if kernel_root and not m:
m = HeaderScanner.re_rel_dir.match(line)
relative = True
if not m: return
header = m.group(1)
if from_file:
self.files.add(from_file)
if kernel_root and relative:
hdr_dir = os.path.realpath(os.path.dirname(from_file))
hdr_dir = hdr_dir.replace("%s/" % os.path.realpath(kernel_root),
"")
if hdr_dir:
_prefix = "%s/" % hdr_dir
else:
_prefix = ""
header = "%s%s" % (_prefix, header)
if not header in self.headers:
self.headers[header] = set()
if from_file:
if verboseFind:
print("=== %s uses %s" % (from_file, header))
self.headers[header].add(from_file)
def parseFile(self, path, arch=None, kernel_root=None):
"""parse a given file for Linux headers"""
if not os.path.exists(path):
return
# since tokenizing the file is very slow, we first try a quick grep
# to see if this returns any meaningful results. only if this is true
# do we do the tokenization"""
try:
f = open(path, "rt")
except:
print("!!! can't read '%s'" % path)
return
hasIncludes = False
for line in f:
if (HeaderScanner.re_combined.match(line) or
(kernel_root and HeaderScanner.re_rel_dir.match(line))):
hasIncludes = True
break
if not hasIncludes:
if verboseSearch: print("::: " + path)
return
if verboseSearch: print("*** " + path)
list = cpp.BlockParser().parseFile(path)
if list:
macros = kernel_known_macros.copy()
if kernel_root:
macros.update(self.config)
if arch and arch in kernel_default_arch_macros:
macros.update(kernel_default_arch_macros[arch])
list.optimizeMacros(macros)
list.optimizeIf01()
includes = list.findIncludes()
for inc in includes:
self.checkInclude(inc, path, kernel_root)
def getHeaders(self):
"""return the set of all needed kernel headers"""
return set(self.headers.keys())
def getHeaderUsers(self,header):
"""return the set of all users for a given header"""
return set(self.headers.get(header))
def getAllUsers(self):
"""return a dictionary mapping heaaders to their user set"""
return self.headers.copy()
def getFiles(self):
"""returns the set of files that do include kernel headers"""
return self.files.copy()
##########################################################################
##########################################################################
##### #####
##### H E A D E R F I N D E R #####
##### #####
##########################################################################
##########################################################################
class KernelHeaderFinder:
"""a class used to scan the kernel headers themselves."""
# this is different
# from a HeaderScanner because we need to translate the path returned by
# HeaderScanner.getHeaders() into possibly architecture-specific ones.
#
# for example, <asm/XXXX.h> needs to be translated in <asm-ARCH/XXXX.h>
# where ARCH is appropriately chosen
# here's how to use this:
#
# scanner = HeaderScanner()
# for path in <your list of user sources>:
# scanner.parseFile(path)
#
# used_headers = scanner.getHeaders()
# finder = KernelHeaderFinder(used_headers, [ "arm", "x86" ],
# "<kernel_include_path>")
# all_headers = finder.scanForAllArchs()
#
# not that the result of scanForAllArchs() is a list of relative
# header paths that are not bracketed
#
def __init__(self,headers,archs,kernel_root,kernel_config):
"""init a KernelHeaderScanner,
'headers' is a list or set of headers,
'archs' is a list of architectures
'kernel_root' is the path to the 'include' directory
of your original kernel sources
"""
if len(kernel_root) > 0 and kernel_root[-1] != "/":
kernel_root += "/"
self.archs = archs
self.searched = set(headers)
self.kernel_root = kernel_root
self.kernel_config = kernel_config
self.needed = {}
self.setArch(arch=None)
def setArch(self,arch=None):
self.curr_arch = arch
self.arch_headers = set()
if arch:
self.prefix = "asm-%s/" % arch
else:
self.prefix = None
def pathFromHeader(self,header):
path = header
if self.prefix and path.startswith("asm/"):
path = "%s%s" % (self.prefix, path[4:])
return path
def pathToHeader(self,path):
if self.prefix and path.startswith(self.prefix):
path = "asm/%s" % path[len(self.prefix):]
return "%s" % path
def setSearchedHeaders(self,headers):
self.searched = set(headers)
def scanForArch(self):
fparser = HeaderScanner(config=self.kernel_config)
workqueue = []
needed = {}
for h in self.searched:
path = self.pathFromHeader(h)
if not path in needed:
needed[path] = set()
workqueue.append(path)
i = 0
while i < len(workqueue):
path = workqueue[i]
i += 1
fparser.parseFile(self.kernel_root + path,
arch=self.curr_arch, kernel_root=self.kernel_root)
for used in fparser.getHeaders():
path = self.pathFromHeader(used)
if not path in needed:
needed[path] = set()
workqueue.append(path)
for user in fparser.getHeaderUsers(used):
needed[path].add(user)
# now copy the arch-specific headers into the global list
for header in needed.keys():
users = needed[header]
if not header in self.needed:
self.needed[header] = set()
for user in users:
self.needed[header].add(user)
def scanForAllArchs(self):
"""scan for all architectures and return the set of all needed kernel headers"""
for arch in self.archs:
self.setArch(arch)
self.scanForArch()
return set(self.needed.keys())
def getHeaderUsers(self,header):
"""return the set of all users for a given header"""
return set(self.needed[header])
def getArchHeaders(self,arch):
"""return the set of all <asm/...> headers required by a given architecture"""
return set() # XXX: TODO
#####################################################################################
#####################################################################################
##### #####
##### C O N F I G P A R S E R #####
##### #####
#####################################################################################
#####################################################################################
class ConfigParser:
"""a class used to parse the Linux kernel .config file"""
re_CONFIG_ = re.compile(r"^(CONFIG_\w+)=(.*)$")
def __init__(self):
self.items = {}
self.duplicates = False
def parseLine(self, line):
line = line.strip()
# skip empty and comment lines
if len(line) == 0 or line[0] == "#":
return
m = ConfigParser.re_CONFIG_.match(line)
if not m: return
name = m.group(1)
value = m.group(2)
if name in self.items: # aarg, duplicate value
self.duplicates = True
self.items[name] = value
def parseFile(self,path):
f = file(path, "r")
for line in f:
if len(line) > 0:
if line[-1] == "\n":
line = line[:-1]
if len(line) > 0 and line[-1] == "\r":
line = line[:-1]
self.parseLine(line)
f.close()
def getDefinitions(self):
"""retrieve a dictionary containing definitions for CONFIG_XXX"""
return self.items.copy()
def __repr__(self):
return repr(self.items)
def __str__(self):
return str(self.items)