Mastering Variability Challenges in Linux and Related Highly

Mastering Variability Challenges in Linux and Related Highly
Mastering Variability Challenges in
Linux and Related
Highly-Configurable System
Software
Der Technischen Fakultät der
Friedrich-Alexander-Universität Erlangen-Nürnberg
zur Erlangung des Doktorgrades
D OKTOR -I NGENIEUR
vorgelegt von
Reinhard Tartler
aus Nürnberg
Als Dissertation genehmigt von
der Technischen Fakultät
Friedrich-Alexander-Universität Erlangen-Nürnberg
Tag der mündlichen Prüfung: 8. August 2013
Vorsitzender des Promotionsorgans: Prof. Dr. Marion Merklein
Gutachter: Prof. Dr.-Ing. Wolfgang Schröder-Preikschat
Dr.-Ing. Sven Apel
Beherrschung von Variabilitätsproblemen in
Linux und verwandter hoch-konfigurierbarer
Systemsoftware
Die von moderner Systemsoftware angebotenen Konfigurationsmechanismen
erlauben die Anpassung an eine breite Auswahl von unterstützten Hardwarearchitekturen und Anwendungsdomänen. Linux ist hierbei ein sowohl
prominentes als auch gutes Beispiel: In Version 3.2 bietet Linux mehr als 12,000
vom Benutzer steuerbare Konfigurationsschalter, mit stark steigender Tendenz.
Dieses hohe Maß an Konfigurierbarkeit stellt Entwickler vor große Herausforderungen. Zum einen muss die in den Konfigurationswerkzeugen deklarierte, mit der im Programmtext umgesetzten Variabilität in Einklang gehalten
werden. Bei händischer Durchführung stellt dies einen mühsamen und fehleranfälligen Arbeitsschritt dar. Zum anderen erschweren die im Programmtext
programmierten Alternativen den Einsatz von statischen Analysewerkzeugen.
Schließlich macht die überwältigende Anzahl an Konfigurationsoptionen es
Systemintegratoren und Entwicklern schwer, die für einen gegebenen Anwendungsfall beste Belegung der Konfigurationsschalter zu finden.
In dieser Arbeit analysiere ich die Variabilitätsmechanismen in Linux und
verwandter Systemsoftware, bei der ich im ersten Schritt viele Inkonsistenzen
zwischen der Deklaration und Umsetzung von Variabilität aufdecke. Viele
dieser Inkonsistenzen sind dabei nachweislich tatsächliche Programmierfehler.
Es stellt sich dabei heraus, dass das extrahierte Variabilitätsmodell auch für
weitere Anwendungen nützlich ist. So hilft das formalisierte Modell Entwicklern bestehende statische Analysewerkzeuge effektiver einsetzen zu können.
Dies erlaubt die systematische Aufdeckung von Programmfehlern, die in selten
gewählten Konfigurationen verborgen sind. Darüber hinaus ermöglicht mein
Ansatz die Konstruktion einer minimalen Konfiguration mit dem extrahierten
Variabilitätsmodell und einer Laufzeitanalyse des Systems. Dies ermöglicht
es Systemadministratoren einen Linux-Kern mit einer deutlich verkleinerten
Angriffsfläche zu übersetzen und zu betreiben, was die Sicherheit des Gesamtsystems deutlich erhöht. Letztendlich erlaubt mein Ansatz die ganzheitliche
Beherrschung der Variabilität in Linux zur Übersetzungszeit über die Sprachgrenzen der eingesetzten Werkzeuge KCONFIG, MAKE und CPP, hinweg.
Mastering Variability Challenges in Linux and
Related Highly-Configurable System
Software
The compile-time configuration mechanisms of modern system software allow
the adaptation to a broad range of supported hardware architectures and
application domains. Linux is hereby a both prominent and good example: In
version 3.2, Linux provides more than 12,000 user-configurable configuration
options, growing rapidly. This high amount of configurability imposes big
challenges for developers. First, the declared variability in the configuration
tooling, and what is actually implemented in the code, have to be kept in
sync. If performed manually, this is a tedious and error-prone task. Second,
alternatives implemented in the code make the use of tools for static analysis
challenging. Finally, the overwhelming amount of configuration options make
finding the best configuration for a given use-case hard for system integrators
and developers.
In this thesis, I analyze the variability mechanisms in Linux and related
system software, in which I reveal many inconsistencies between the variability
declaration and implementation. Many of these inconsistencies are hereby
provably actual programming errors. It turns out that the extracted variability
model is useful for additional applications. The formalized model helps
developers with employing existing tools for static analysis more effectively.
This allows the systematic revelation of bugs that are hidden under seldom
tested configurations. Moreover, my approach enables the construction of a
minimal Linux configuration with the extracted variability model and a runtime analysis of the system. This enables system administrators to compile and
operate a Linux kernel with significantly reduced attack-surface, which makes
the system more secure. In the end, my approach allows the holistic mastering
of compile-time variability across the language barriers of the employed tools
KCONFIG, MAKE and CPP.
Acknowledgements
Many people have supported me in writing this thesis, for which I am very
thankful. Special thanks go to my supervising professor W OLFGANG S CHRÖDER P REIKSCHAT, who has given me the opportunity to work in a tremendous
environment of research and teaching! I am thankful for having met D ANIEL
L OHMANN, who has not only supervised my master thesis, but has always
acted as a thoughtful and great mentor.
I am very thankful for the enduring support from my wife K ATHRIN, who
continuously motivated me during both my studies and research. Also many
thanks to my parents, and my in-laws I NGRID and B URCKHARD, for their
assistance and backing that I received over the many years.
I wish all the best for the future to the VAMOS team, which at the time of
writing consisting of C HRISTIAN D IETRICH and C HRISTOPH E GGER, with whom
I kick-started the very first ideas of this dissertation. Also many thanks to the
“tracewars” team: VALENTIN R OTHBERG, A NDREAS R UPRECHT and B ERNHARD
H EINLOTH for the great time we spent together. I wish all the best to S TEFAN
H ENGELEIN and M ANUEL Z ERPIES for the future.
I’m am overwhelmed by the friendliness, openness and support of the
research staff at the Department of Computer Science 4. I’m happy to have
met J ÜRGEN K LEINÖDER, who has enabled me to join the group. It was more
than pleasure to work with J ULIO S INCERO—thank you for the great time.
Many thanks to my former colleagues M ICHAEL G ERNOTH, FABIAN S CHELER,
P ETER U LBRICH, M ORITZ S TRÜBE, B ENJAMIN O ECHSLEIN, and T OBIAS D ISTLER,
who always assisted me with idea and provided inspiration. My respect and
thanks also go to the rest of the remarkable and very vivid team of researchers
of this department, who made my former workplace a joyful and inspirational,
and successful place.
Thanks!
Erlangen, August 2013
vii
Contents
1. Introduction
1
1.1. Variability in Configurable System Software . . . . . . . . . . .
1.2. Problem Statement and Contributions of this Thesis . . . . . . .
1.3. Structure of this Thesis . . . . . . . . . . . . . . . . . . . . . . .
2. Problem Analysis and State of the Art
2.1. Variability Realization in Linux . . . . . . . . . . . . . .
2.1.1. The Variability Declaration . . . . . . . . . . . .
2.1.2. Variability Implementation . . . . . . . . . . . . .
2.1.3. Granularity of Variability Implementations . . . .
2.1.4. Generalization . . . . . . . . . . . . . . . . . . .
2.2. Related Work . . . . . . . . . . . . . . . . . . . . . . . .
2.2.1. Program Families and Software Product Lines . .
2.2.2. Management of Variability in Operating Systems
2.2.3. Variability-Aware Analysis of Source Code . . . .
2.2.4. Implementation of Features in Operating Systems
2.3. Chapter Summary . . . . . . . . . . . . . . . . . . . . .
2
4
5
7
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
3. Extraction and Validation of Variability Implementations
3.1. The Variability Language of the Linux Kernel . . . . . . . . . . .
3.1.1. General Characteristics of the Linux Configuration Model
3.1.2. Translation into Propositional Logic . . . . . . . . . . . .
3.1.3. Analysis of the KCONFIG Model . . . . . . . . . . . . . .
3.1.4. Related Work and Limitations . . . . . . . . . . . . . . .
3.1.5. Summary: The Variability Language of the Linux Kernel
3.2. Robust Extraction of Variability from the Linux Build System . .
3.2.1. The Role of the Build System . . . . . . . . . . . . . . .
3.2.2. Related Approaches for Build System Variability Extraction
3.2.3. Common K BUILD Idioms . . . . . . . . . . . . . . . . . .
3.2.4. Challenges in Build-System Analysis . . . . . . . . . . .
3.2.5. Experimental Probing for Build-System Variability . . . .
3.2.6. Benefits and Limitations . . . . . . . . . . . . . . . . . .
3.2.7. Application to Real-World Software Projects . . . . . . .
9
11
12
14
14
15
15
16
18
19
22
23
25
26
27
28
29
30
31
31
33
34
35
38
43
44
ix
Contents
3.2.8. Summary: Robust Extraction of Variability from the
Linux Build System . . . . . . . . . . . . . . . . . . . . .
3.3. Variability with the C Preprocessor . . . . . . . . . . . . . . . .
3.3.1. The use of C preprocessor (CPP) in Linux . . . . . . . .
3.3.2. Conditional Compilation . . . . . . . . . . . . . . . . . .
3.3.3. Expressing the Variability of CPP in Propositional Formulas
3.3.4. Non-Boolean expression . . . . . . . . . . . . . . . . . .
3.3.5. Extraction of Code Variability in B USY B OX . . . . . . . .
3.3.6. Experimental Validation of the Extracted Formula . . . .
3.3.7. Further Related Work . . . . . . . . . . . . . . . . . . .
3.3.8. Summary: Variability with the C Preprocessor . . . . . .
3.4. Chapter Summary . . . . . . . . . . . . . . . . . . . . . . . . .
4. Case Studies
4.1. Configuration Consistency . . . . . . . . . . . . . . . . . . . .
4.1.1. Manifestations of Configuration Inconsistencies . . . .
4.1.2. Ensuring Configuration Consistency . . . . . . . . . .
4.1.3. Application on Linux . . . . . . . . . . . . . . . . . . .
4.1.4. Improvements by Makefile Constraints . . . . . . . . .
4.1.5. Accuracy . . . . . . . . . . . . . . . . . . . . . . . . .
4.1.6. General Applicability of the Approach . . . . . . . . .
4.1.7. Further Related Work . . . . . . . . . . . . . . . . . .
4.1.8. Summary: Configuration Consistency . . . . . . . . . .
4.2. Configuration Coverage . . . . . . . . . . . . . . . . . . . . .
4.2.1. Configurability versus Static Analysis . . . . . . . . . .
4.2.2. Configuration Coverage in Linux . . . . . . . . . . . .
4.2.3. Partial Configurations . . . . . . . . . . . . . . . . . .
4.2.4. Approach for Maximizing the Configuration Coverage
4.2.5. Experimental Results . . . . . . . . . . . . . . . . . . .
4.2.6. Discussion . . . . . . . . . . . . . . . . . . . . . . . . .
4.2.7. Summary: Configuration Coverage . . . . . . . . . . .
4.3. Configuration Tailoring . . . . . . . . . . . . . . . . . . . . . .
4.3.1. Kernel-Configuration Tailoring . . . . . . . . . . . . .
4.3.2. Evaluation . . . . . . . . . . . . . . . . . . . . . . . . .
4.3.3. Discussion . . . . . . . . . . . . . . . . . . . . . . . . .
4.3.4. Summary: Configuration Tailoring . . . . . . . . . . .
4.4. Chapter Summary . . . . . . . . . . . . . . . . . . . . . . . .
5. Summary and Conclusion
50
51
51
52
53
58
59
60
60
61
63
65
. 67
. 67
. 70
. 74
. 78
. 79
. 79
. 81
. 81
. 83
. 83
. 85
. 90
. 92
. 96
. 100
. 104
. 105
. 106
. 108
. 113
. 115
. 116
117
5.1. Achieved Accomplishments . . . . . . . . . . . . . . . . . . . . 117
5.2. Contributions . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119
5.3. Going Further and Future Work . . . . . . . . . . . . . . . . . . 120
x
Contents
A. Translating K CONFIG into Propositional Logic
123
A.1. Basic Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123
A.2. Choices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124
A.3. Selects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125
B. Practical Impact of Configuration Inconsistencies on the development
of the Linux kernel
127
B.1.
B.2.
B.3.
B.4.
The Linux Development Process .
Validity and Evolution of Defects
Causes for Defects . . . . . . . .
Reasons for Rule Violations. . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
C. Build-System Probing
.
.
.
.
.
.
.
.
. 127
. 128
. 129
. 131
133
C.1. Basic Algorithms . . . . . . . . . . . . . . . . . . . . . . . . . . 133
C.2. Exemplary Operation . . . . . . . . . . . . . . . . . . . . . . . . 136
C.3. Qualitative Analysis of the Calculated Source File Constraints . 140
D. Algorithms for Calculating the Configuration Coverage
143
D.1. The Naïve Approach . . . . . . . . . . . . . . . . . . . . . . . . 144
D.2. The Greedy Approach . . . . . . . . . . . . . . . . . . . . . . . 144
E. Compilation of Patches that Result from the VAMPYR Experiments
147
xi
List of Figures
2.1.
2.2.
2.3.
2.4.
2.5.
Fine and coarse-grained variability in Linux . . . . . . . .
Screen shot of KCONFIG . . . . . . . . . . . . . . . . . . .
Technical levels of variability realization points . . . . . .
Variability declaration versus variabibility implementation
The SPL approach at a glance . . . . . . . . . . . . . . . .
3.1.
3.2.
3.3.
3.4.
A simple, Boolean KCONFIG item . . . . . . . . . . . . . . . .
Distribution of variability points in Linux version 3.2 . . . . .
Abstract view on the variability implemented by K BUILD . . .
Abstract model of a hierarchic build system with subdirectories
and conditionally compiled source files . . . . . . . . . . . . .
The toolchain for building the F IASCO µ-Kernel . . . . . . . .
Abstract view on the variability that is implemented by C preprocessor . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
A source file with three #ifdef blocks . . . . . . . . . . . . . .
The configuration item FEATURE_CHECK_UNICODE_IN_ENV . . .
3.5.
3.6.
3.7.
3.8.
4.1.
4.2.
4.3.
4.4.
4.5.
4.6.
4.7.
4.8.
.
.
.
.
.
.
.
.
.
.
General approach for finding configuration inconsistencies . .
Principle of operation of the UNDERTAKER tool . . . . . . . . .
Exemplary output of the UNDERTAKER tool . . . . . . . . . . .
Processing time for 27,166 Linux source files . . . . . . . . . .
Basic workflow with the UNDERTAKER tool . . . . . . . . . . .
Response time for the submitted patches . . . . . . . . . . . .
Maximizing configuration coverage (CC) at a glance . . . . .
Workflow of the VAMPYR configuration-aware static-analysis
tool driver . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4.9. Kernel-configuration tailoring workflow . . . . . . . . . . . .
4.10.Evolution of KCONFIG features enabled over time . . . . . . .
4.11.Comparison of features and compiled source files . . . . . . .
4.12.Reduction in compiled source files for the tailored kernel . . .
4.13.Analysis of reply rates of the Linux, Apache, MySQL and PHP
(LAMP)-based server . . . . . . . . . . . . . . . . . . . . . . .
4.14.Analysis of the test results from the Bonnie++benchmark . . .
.
.
.
.
.
10
12
13
15
17
. 26
. 32
. 38
. 39
. 49
. 54
. 58
. 59
.
.
.
.
.
.
.
71
72
73
76
77
78
93
. 94
. 106
. 110
. 110
. 111
. 112
. 114
B.1. Evolution of defect blocks over various Kernel versions . . . . . 129
xiii
List of Figures
C.1.
C.2.
C.3.
C.4.
Probing step 1 . . . . . .
Probing Steps 2 and 3 .
Probing Step 4 and 5 . .
Influence of the KCONFIG
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
selection in the
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
F IASCO build process
. 136
. 137
. 138
. 139
D.1. Internal structure of the translation of conditional blocks . . . . 144
D.2. Naïve variant . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145
D.3. Greedy variant . . . . . . . . . . . . . . . . . . . . . . . . . . . 146
xiv
List of Tables
2.1. Feature growth in Linux over the past years . . . . . . . . . . .
3.1. Quantitative overview over software projects that employ KCON FIG for variability management . . . . . . . . . . . . . . . . . .
3.2. KCONFIG features and constraints types in Linux/x86 version 3.0.
3.3. Characteristics the translated KCONFIG model for Linux/x86
version 3.0. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.4. Occurrences of selected language features in the makefiles of
Linux 3.2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.5. Direct quantitative comparison over Linux versions over the last
5 years . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.6. Breakdown of source files in B USY B OX . . . . . . . . . . . . . .
3.7. C Preprocessor directives related to conditional compilation . .
4.1.
4.2.
4.3.
4.4.
4.5.
9
25
28
29
37
45
48
52
blocks and defects per subsystem in Linux version 2.6.35 75
Critical versus non-critical patches . . . . . . . . . . . . . . . . 77
Results of the configuration consistency analysis on Linux v3.2 . 78
Further configuration consistency analyses . . . . . . . . . . . . 80
Quantification over variation points across selected architectures in Linux v3.2 . . . . . . . . . . . . . . . . . . . . . . . . . 88
4.6. Results of the VAMPYR tool with GCC 4.6 on Linux v3.2, F IASCO
and B USY B OX. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
4.7. Classification of GCC warnings and errors revealed by the VAMPYR
tool on Linux/arm . . . . . . . . . . . . . . . . . . . . . . . . . 99
4.8. Results of the kernel-configuration tailoring . . . . . . . . . . . 113
#ifdef
C.1. Quantitative analysis of two K BUILD variability extraction implementations . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141
xv
1
Introduction
I know of no feature that is always needed.
When we say that two functions are almost
always used together, we should remember
that “almost” is an euphemism for “not”.
(Parnas, 1979, [Par79])
System software, which includes operating systems, manages system resources for application software and end-users. A major challenge is to fulfill
its task unnoticed despite the competition of different requirements such as
efficiency or security. This is particularly important when applications run in a
resource constrained environment, which is commonly found in embedded systems. For such scenarios, both, the applications and the system software, need
to be tailored for both, the available hardware, and the use-cases for which
the system was built. The other extreme constitute scenarios that involve
general-purpose operating systems such as Microsoft Windows or popular
Linux distributions, in which neither the exact hardware configuration, nor the
applications that they will run, are known at development time: This use-case
(generally) requires a configuration that enables as many features as possible.
Both, the embedded system scenario as well as the case of general-purpose
operating systems, require the handling of a large number of requirements at
the same time. In fact, system software is among the most variable pieces of
software at all. Usually the software allows configuring the required functionality, the behavior at run time, or allows the execution on different execution
platforms. An excellent implementation of static configurability is particularly
important for embedded systems, which run on inexpensive, mass-produced
hardware and are usually quite resource constrained on available CPU, mem-
1
Chapter 1. Introduction
ory, and flash footprints. Usually, static configuration is part of the build
process and decides what (and how) features get included in the resulting
products.
A good example for a highly-configurable piece of system software is the
Linux kernel, which in version v3.2 provides more than 12,000 configuration
options, growing at a rapid pace. This successful open-source project provides
an operating-system kernel that is used in a variety of systems, ranging from
µ-controllers to high-performance super computers. The huge number of
contributors and the impressive pace of integrated new changes [CKHM12]
make the Linux kernel the most successful open-source project. This attracts
a large number of companies such as Google, IBM, Intel, or Novell, which
invest into the development of the Linux kernel for integration into their own
products. These developments contribute to making the Linux kernel suited
for a large number of scenarios and require Linux developers to organize the
features in a way that they can be managed by users such as system integrators,
who want to integrate and adapt Linux for their needs.
Depending on the context, different goals drive the need for customizability.
Often, performance is the driver for customizability. For embedded systems,
the power consumption or memory footprint may be the main driver. An
important goal of making the system customizable is to adapt gracefully to
all of these, and possibly conflicting needs. While static configuration allows
satisfying the requirements of many stakeholders, it also comes at the cost of
maintenance overhead. Therefore, means for allowing customizability need to
be planned and managed.
In order to achieve software-reuse, the commonalities and differences of
feature implementations need to be managed in a way that users, such as developers and system integrators, are able to select the best combination of features
according to the given requirements. This work explores the challenges and
yet unreached opportunities in the configurability and variability-management
techniques for today’s system software. The Linux kernel receives a particular
focus because of two reasons: First, this piece of software represents the largest
and most variable piece of software that is available for research. Second,
the employed techniques and tooling has inspired a large number of other
software projects to which the results of this work apply as well. I therefore
expect that the results of this work can be applied to many important software
projects that thousands of people use every day.
1.1. Variability in Configurable System Software
Variability management in systems software includes two separate – but related
– aspects: declaration of what can be configured and its implementation.
2
1.1. Variability in Configurable System Software
To illustrate, consider the Linux feature HOTPLUG_CPU. This feature is declared in Linux version 2.6.30 in the KCONFIG language, which was specifically
designed for describing features in Linux:
config HOTPLUG_CPU
bool "Support for hot-pluggable CPUs"
dependson SMP && HOTPLUG
---help--Say Y here to allow turning CPUs off and on. CPUs can be
controlled through /sys/devices/system/cpu.
( Note: power management support will enable this option
automatically on SMP systems. )
Linux developers implement features that have been declared in this way
in the code and the build scripts. In most cases, they do this by means
of conditional compilation. In the implementation, this can look like the
following excerpt from the source file kernel/smp.c:
switch (action) {
case CPU_PREPARE:
#ifdef CONFIG_CPU_HOTPLUG
case CPU_UP_CANCELED:
[...]
#endif
};
The current state of the art is to address the variability declaration, as done in
the KCONFIG example above, and implementation of feature implementations,
as shown with the #ifdef block above, independently. Without continuous
consistency checks, this non-homogeneous handling of variability declaration
and implementation is prone to a number of problems, including configuration
defects and bugs. In this particular example, the problem is in the misspelling
of the CPP identifier CONFIG_CPU_HOTPLUG, which caused the code inside this
#ifdef block to not be compiled at all. This was corrected in the Linux v2.6.31
release by renaming it to CONFIG_HOTPLUG_CPU, fixing a serious memory leak
when unplugging processors during run time.
In principle, there are two ways to address the problem. The first one
is handle both, the variability declaration and implementation, within the
same tool and language. In context of system software, techniques such as
Feature-Oriented Programming (FOP) [Bat04] or Aspect-Oriented Programming (AOP) [Loh+12; Loh09] have been proposed and evaluated. Unfortunately, these approaches cannot be applied easily to existing systems without a
major reengineering effort. In practice, system software is (mostly) developed
with the tools MAKE and the CPP [Spi08] despite all the disadvantages with
respect to understandability and maintainability this approach is known for
[LKA11; SC92]. The other way to address the problem is to implement the
continuous consistency checking with tools that understand both, the variability declaration and implementation, of today’s system software. This is the
3
Chapter 1. Introduction
approach that I follow in this thesis.
1.2. Problem Statement and Contributions of this Thesis
As one can imagine, the inconsistencies between the variability declaration
and the implementation can easily cause problems. In this thesis, I investigate
the resulting challenges by answering the following three questions:
Question 1: Do configuration mechanisms itself cause bugs? If yes, what kind
of bugs arise and what can be done tool-wise to address them?
A satisfactory answer to these questions requires the formal extraction for
non-homogeneous variability mechanisms in a form that allows a systematic
comparison and reasoning. However, what are the configuration mechanisms,
and how do they work in detail? How do inconsistencies manifest and how
severe are their effects in practice?
My take on this problem is to first analyze the configuration mechanisms
in Linux with a special focus on variability. Based on this analysis, I present
extraction tools, which can be used both to answer this question, and lead to
tools for the practical use by developers to systematically identify and locate
software bugs in the source code.
Question 2: Does configuration-based variability hinder static analysis?
System software, which is well-known for its hard-to-fix and complex software
bugs (such as protocol violations, concurrency issues, etc.), greatly benefits
from static analysis, which allows to identify software bugs in the source code
without actually having to run the system. The many works that have been
published in the last years (e.g., [Jin+12; Pal+11; Tan+07; Kre+06; LZ05;
Ern+00; Eng+01] to name a few) on this topic show the high relevance in
the scientific system community. However, none of these works address the
problem of implementation variability.
This work is the first to address this gap. First, I reuse the aforementioned
variability extractors to scope the challenges of configurability for the static
analysis of source code. Then, I propose a practical solution that greatly
extends the coverage and applicability of existing scanners, which allows
revealing additional problems hidden by the configuration mechanisms.
Question 3: How to tailor Linux for an a-priori known scenario?
Linux is both, a widely deployed and a highly configurable piece of software.
However, on Linux/x86 alone, the more than 5,000 user-configurable features
Linux v3.2 offers make finding the best configuration very challenging. In
cases where the requirements of the system are well defined, such as a Linux
webserver in the cloud, enabling all available functionality wastes unnecessary
4
1.3. Structure of this Thesis
resources and decreases the overall system security by exposing unnecessary
attack surface to malicious hackers. In this thesis, I mitigate the problem
by presenting an approach that calculates a Linux kernel configuration that
includes only the actually required functionality.
1.3. Structure of this Thesis
In order to answer these three questions, in this thesis I first analyze the
variability mechanisms in Linux and review the state of the art by discussing
the related work in Chapter 2. Then, in Chapter 3, I formalize the variability
constraints into a common representation that allows relating variability on
an abstract level. For Linux, this requires the extraction of variation points
from the #ifdef statements found in the implementation (Section 3.3), from
KCONFIG (Section 3.1), and the build system K BUILD (Section 3.2). The result
is a holistic variability model, which I apply to three different applications:
1. Configuration consistency (Section 4.1)
2. Configuration coverage (Section 4.2)
3. Configuration tailoring (Section 4.3)
The lack of configuration consistency results in variation points that are
only seemingly configurable. The severity of such inconsistencies range from
unnecessary (i.e., dead) and superfluous (i.e., undead code) to unintended
programming errors that can result in serious bugs. As part of my research,
123 patches that fix 364 variability defects have been submitted to the Linux
kernel maintainers, which have accepted fixes for 147 defects.
Ensuring configuration coverage allows the application of static analysis
over the complete code base (i.e., including all branches induced by #elif
statements) with tools that require a fully configured source tree for analysis.
This is a typical requirement for state-of-the art static analysis tools (e.g.,
Coverity, Clang-Scan, . . . ) for C/C++ projects, because otherwise full typechecking is not possible. The tools I present in this thesis can significantly
increase the configuration coverage to up to 95.5 percent on Linux/x86. By
using the compiler itself, the rate of detected issues significantly increases by
up to 77.4 percent on Linux/arm.
The technique configuration tailoring basically allows to automatically
construct a Linux KCONFIG configuration based on a system trace. The idea is
to observe what functionally a running system accesses in the kernel in order
to deduce which configuration options are required for a given use-case. All
other options, which are not used by the traced system, unnecessarily increase
the attack surface of the kernel. In a typical web-server scenario, the tailored
5
Chapter 1. Introduction
kernel contains only 10 percent of the code that the distribution provided
kernel contained. Therefore, by constructing a kernel configuration that
enables less functionality, configuration tailoring provides a fully-automated
approach that makes a Linux system significantly more secure without run-time
overhead.
The thesis summarizes the contributions and concludes in Chapter 5.
6
2
Problem Analysis and State of the Art
The kernel is huge and bloated. . . and
whenever we add a new feature, it only gets
worse.
(Linus Torvalds, LinuxCon’09)
A Linux developer has to learn a number of different tools and languages
for properly integrating a new feature into Linux. For once, most code in
Linux is implemented in the C Programming Language [ISO99]. Introducing
new source files requires programmers to engage with the MAKE language and
the Linux specific idioms that control the conditional compilation of source
files. Lastly, user-controllable features need to be added to the configuration
description in KCONFIG. This observation is not specific to Linux, but also
applies to other system software such as Open Solaris, FreeBSD, Fiasco or
eCos. Does this have to be so complicated?
One important reason for this complexity is the fact that the mentioned tools
and languages operate on different levels of abstraction and granularity. This
allows the programmer to design the variable parts of the software according
to the requirements of the same technical level that he1 works on. To illustrate,
assume a driver developer who introduces a new device driver for Linux.
At the beginning of the task (but after the design phase of the feature) he
declares a new user-visible and configurable feature in KCONFIG. Based on the
design, he decides to introduce a new .c implementation file, which is the
means of the C programming language for a software module. Therefore, he
1
In this work, the examples use the male form for anonymous actors. In no way is this meant as a
discrimination against female developers!
7
Chapter 2. Problem Analysis and State of the Art
instructs the makefile of the directory that contains his new file to compile it
only when the user requests the driver in KCONFIG. Since the driver needs a
special initialization on some specific architecture, he annotates these lines
with #ifdef statements to ensure that the code is only compiled and executed
on the architectures that he specifies in the #ifdef expression. From the
view of an (experienced) programmer, this process is both natural and easy
to follow. While this may seem natural to such programmers, what are the
consequences of this approach with respect to variability management, and in
what ways can this be further improved?
In this chapter, I study the methods for implementing and integrating new
alternative and optional functionality in Linux in detail. This leads to a
conceptional analysis of the implementation configurability in Linux and the
challenges that result for variability management. After sketching an overview
over the presented solution, I discuss and distinguish the related work.
Related Publications
The ideas and results of this chapter have partly also been published as:
8
[Die+12b]
Christian Dietrich, Reinhard Tartler, Wolfgang SchröderPreikschat, Daniel Lohmann. “Understanding Linux Feature Distribution”. In: Proceedings of the 2nd AOSD Workshop on Modularity in Systems Software (AOSD-MISS ’12). (Potsdam, Germany, Mar. 27, 2012). Edited by Christoph Borchert, Michael
Haupt, and Daniel Lohmann. New York, NY, USA: ACM Press,
2012. DOI: 10.1145/2162024.2162030
[Die+12a]
Christian Dietrich, Reinhard Tartler, Wolfgang SchröderPreikschat, Daniel Lohmann. “A Robust Approach for Variability Extraction from the Linux Build System”. In: Proceedings
of the 16th Software Product Line Conference (SPLC ’12). (Salvador, Brazil, Sept. 2–7, 2012). Edited by Eduardo Santana
de Almeida, Christa Schwanninger, and David Benavides. New
York, NY, USA: ACM Press, 2012, pages 21–30. DOI: 10.1145/
2362536.2362544
2.1. Variability Realization in Linux
features
#ifdef blocks
source files
2.6.12 (2005)
2.6.20
2.6.25
2.6.30
2.6.35 (2010)
5,338
7,059
8,394
9,570
11,223
57,078
62,873
67,972
79,154
84,150
15,219
18,513
20,609
23,960
28,598
Relative growth (5 years)
110%
47%
88%
version
Table 2.1.: Feature growth in Linux over the past years
2.1. Variability Realization in Linux
Linux supports a wide range of hardware platforms and device drivers. It is
particularly dominant in both, today’s server systems with a total market share
of 20.7 percent of all server revenues [IDC12a], and mobile telephone devices,
due to the commercial success of the Android Platform with 104.8 million
sold devices [IDC12b]. This alone makes Linux a very interesting subject for
scientific study. Even more impressive is the rate in which new functionality
is integrated, as shown in Table 2.1. Each new Linux release introduces new
functionality, such as device drivers, hardware platforms, network protocols,
and so on. Most of these features are implemented in a way that allows users
to choose which of them to include in the resulting Linux kernel. To cite the
development statistics published by the Linux Foundation [CKHM12]:
“The kernel which forms the core of the Linux system is the result
of one of the largest cooperative software projects ever attempted.
Regular 2-3 month releases deliver stable updates to Linux users,
each with significant new features, added device support, and improved performance. The rate of change in the kernel is high and
increasing, with over 10,000 patches going into each recent kernel release. These releases each contain the work of over 1,000
developers representing around 200 corporations.”
Linux would probably not manage to integrate features at this rate without tools for configuration management, such as KCONFIG. However, since
the Linux development has not (yet) investigated and integrated scientific
variability management, could Linux integrate new features even faster with
improved tool support? In order to answer this and similar questions, I first
analyze the employed techniques in Linux, formalize the results, and then
present easy-to-use tools for Linux maintainers and developers as part of this
thesis.
In order to understand the effect of this large amount of variability, it is necessary to highlight the build process that is used for producing bootable Linux
9
Chapter 2. Problem Analysis and State of the Art
Root Feature
Kconfig
selection
1
Kconfig
kbuild
.config
derives from
derives from
fine-grained
variability
coarse-grained
variability
Build scripts
2
3
Source files
autoconf.h
auto.conf
#ifdef CONFIG_HOTPLUG_CPU
Makefile
arch/x86/init.c
arch/x86/entry32.S
arch/x86/...
lib/Makefile
kernel/sched.c
...
...
#endif
CPP
Kbuild
kbuild
drives and controls
gcc -O2 -Wall -c numa.c -o numa.o
4
ld numa.o <...> -o vmlinux
vmlinuz
drivers.ko
Figure 2.1.: Fine and coarse-grained variability in Linux
images and loadable kernel modules with respect to variability management.
This process can be divided into four steps as depicted in Figure 2.1.
Ê Configuration The KCONFIG tool has been designed to support the description, dependencies, and constraints of features in the Linux kernel. It
provides a language to describe a variant model consisting of features
(referred to as configuration options).
The user configures a Linux kernel by selecting features from this model
in a dedicated configuration tool. During the selection process, the
KCONFIG configuration utility implicitly enforces all dependencies and
constraints, so that the outcome is always the description of a valid
variant.
Ë Generation Coarse-grained variability is implemented on the generation
10
2.1. Variability Realization in Linux
step, or more precisely, in the Linux build system, which drives the
process of compiling the Linux kernel into a bootable executable. The
compilation process in Linux is controlled by a set of custom scripts that
interpret a subset of the CPP flags and select which compilation units
should be compiled into the kernel, compiled as a loadable module, or
not compiled at all. This set of scripts form the build system, which is
commonly called K BUILD.
Ì Source Preprocessing Fine-grained variability is implemented by conditional compilation using the C preprocessor. The source code is annotated
with preprocessor directives, which are evaluated in the compilation process. This is the major variability mechanism used in the Linux kernel.
Í Resulting Build Products At the end of the build process, K BUILD links and
assembles the resulting build artifacts, that is, the bootable Linux image
and the loadable kernel modules (LKMs). These artifacts are ready to
use product variants that result from the configuration choice in step Ê.
The variability described in the KCONFIG files and in the source code is
conceptually interconnected and has to be kept consistent. However, there is a
gap between the various tools that are involved during the configuration and
compilation phase of the Linux kernel. In order to close this gap, I propose to
systematically check the features declared in KCONFIG against their use in the
source code and makefiles, and vice versa.
2.1.1. The Variability Declaration
Variability is declared in Linux with the means of the KCONFIG tool, which
provides both text-based as well as graphical front-end programs. These configurators assist users with searching for specific features, presenting descriptive
help texts, and ensuring that the user can only make configuration selections
that are consistent with the logical constraints that the developers specify in the
KCONFIG files. The set of user-configurable features is called the configuration
space.
KCONFIG provides both textual as well as several graphical frontends for
configuring Linux. The screenshot in Figure 2.2 shows the graphical frontend
in action. In this tool, the user navigates through the developer-defined menu
hierarchy and examines what features to enable or disable. The meta-data
includes both help texts as well as dependencies on other features and other
constraints. The KCONFIG language features a number of complex semantics
that allow very sophisticated constraints among the declared features. In
some cases, the tool hides configuration options from the user (I discuss the
semantics of KCONFIG in detail in Section 3.1). This means that the user is
not able to see the complete set of variability points in this configurator, which
11
Chapter 2. Problem Analysis and State of the Art
Figure 2.2.: Screen shot of KCONFIG
makes it even more complicated to overview the complete variability in Linux.
For the sake of simplicity, in this work I consider every configuration option
that is declared in the KCONFIG files as a variability point.
2.1.2. Variability Implementation
In the Linux kernel source code, configurability is implemented with a mostly
idiomatic use of configuration flags in the source code and build-scripts. The
configuration process results in a valid selection of features. These feature
selections are used in the source code and makefiles to realize variability. This
is called the implementation space.
The KCONFIG configuration tool transforms the configuration selection into
CPP flags and makefile variables that feature the prefix CONFIG_. Moreover,
the Linux programming conventions reserve this prefix to denote a KCONFIG
controlled namespace, which programmers should therefore not use for other
purposes. This makes configuration-conditional blocks easy to identify: The
#ifdef expression references one or more CPP identifiers with the prefix
CONFIG_.
12
2.1. Variability Realization in Linux
(Kconfig, configuration script)
F1
F2
F3
Formal Model
l1 : Build System (Shell Scripts, make)
l2 : Preprocessor l3 : Compiler / Language
l4 : Linker
CONFIG_F1 1
#define
CONFIG_F2 0
#define
CONFIG_F3 'm'
configure.h
$(F3_OBJ) : $(F3_SRC)
@...
#ifdef CONFIG_F1
...
#endif
if( CONFIG_F1 ) {
...
}
Implementation Space
(Generators, CPP)
#define
Configuration Space
l0 : Configuration System SECTIONS {
...
F3_mod: {*(.)} }
l5 : Loader
(insmod, ...)
Figure 2.3.: Technical levels of implementation points for variability realizations
(short: variability points). Each level is defined by the implementation
language that is used to implement a variability point. In cases where a
feature is implemented with more than one variability point in different
languages, the implementation spreads across different levels. In this
case, the developers have to ensure that all variability points are kept
consistent across the different levels.
13
Chapter 2. Problem Analysis and State of the Art
2.1.3. Granularity of Variability Implementations
Logically, the development of a new feature, as sketched in the introduction
of this chapter, follows similar steps as the build system does for compiling
the kernel, which can be understood as a set of levels as depicted in Figure 2.3. First, the architecture is chosen to match the compilation target. Then,
the KCONFIG configuration decides on what files to compile. The variability
points on Level l0 are specified in the KCONFIG language. They dominate
the variability points of the higher levels (in Figure 2.3 levels “grow” to the
bottom, i.e., upper layers dominate downwards): If the user deselects the
feature representing the device of the example above, all dominated variation
points are no longer relevant for the compilation process, since K BUILD will
ignore the corresponding source files on Level l1 . On the intra-file granular
level on Level l2 , #ifdef blocks select the architecture or otherwise feature
specific code. Remarkably, this sequence imposes a hierarchy of variability, in
which decisions on the lower layer (e.g., KCONFIG) have a direct impact on the
presence of a variability point of upper levels. Level l3 (Run-time variability),
Level l4 (loadable kernel modules), and higher levels remain out of scope of
this thesis.
2.1.4. Generalization
Conceptionally, the variability declaration as described in Section 2.1.1 and
the variability implementation as described in Section 2.1.2 oppose each other
as depicted in Figure 2.4. On the left side, the user, either a developer or
system integrator, specifies the features that match the given requirements.
This intensional variability can be realized in form of a dedicated tool, such as
the KCONFIG configurator for Linux, or programmatically, such as configure
scripts in the GNU Build System (GBS) [Gnu]. The implementation constraints
on the right side in the figure stem from design and implementation decisions
that the developer specifies in the source code. This is called the extensional
side of variability.
The general idea for creating a holistic view over all variability implementations is to formalize both, the intensional as well as the extensional side, to a
formal model that can be reasoned automatically. This reasoning then allows,
among others, revealing the software bugs outlined above.
In this thesis, I implement this approach for Linux by formalizing the holistic
model by combining submodels for each level. The intensional side is implemented solely on Level l0 , for which I present an extractor in Section 3.1
that extracts the feature constraints and dependencies into the propositional
formula ϕConfiguration . Next, I proceed with the formalization of Level l1 in
Section 3.2 and Level l2 in Section 3.3, which consists of the constraints in the
build system K BUILD and the CPP. The result, ϕK BUILD and ϕCPP , describe the
14
2.2. Related Work
intensional side
extensional side
Application Requirements
Varibility Declaration in
the Configuration Space
Feature Implementation in
the Implementation Space
Implementation Constraints
extraction
Configuration Model
→ϕConfiguration
extraction
cross checks
Implementation Model
→ϕImplemenation
Figure 2.4.: As this thesis will show, system configuration and variability implementation influence each other, both conceptually as well as in the daily work
of system software developers.
model for the implementation space ϕImplementation .
2.2. Related Work
Linux, as a highly-configurable operating system, is well-covered in the literature. This section provides an overview about the most important publications
that are related to the tools and concepts that I present in this thesis.
2.2.1. Program Families and Software Product Lines
The operating systems domain is probably the first area in which variability
management techniques have been discussed. Already in the 70s, Parnas
suggested that
[...] the software designer should be aware that he is not designing
a single program but a family of programs. As discussed in an earlier
paper [Par76], we consider a set of programs to be a program family
if they have so much in common that it pays to study their common
aspects before looking at the aspects that differentiate them. This
rather pragmatic definition does not tell us what pays, but it does
explain the motivation for designing program families. We want
15
Chapter 2. Problem Analysis and State of the Art
to exploit the commonalities, share code, and reduce maintenance
costs. [Par79]
In today’s literature, software product line engineering (SPLE) is the modern
approach to systematically implement program families. A software product
line (SPL) corresponds to a set of software components that can be assembled
together in order to deliver different products in a specific domain. There are
several guidelines for the development of SPLs from scratch [GS04; NC01].
Characteristically, they all also cover business aspects such as stakeholders and
business processes.
Normally, the initial phase is characterized as the domain analysis where
the domain engineering takes place. The commonalities and variability of the
target domain are captured and cataloged for subsequent reuse. Based on
these results, a flexible architecture, which comprises the variability previously
identified, is specified. Finally, the domain implementation represents the
actual coding of the components that can be configured and combined in
order to generate a required product. Figure 2.5 depicts the basic idea of this
process.
SPLE allows companies and its engineers to efficiently create portfolios
of systems in an application domain – that is, software product lines – by
leveraging the commonalities and carefully managing the variability among
them [CN01]. Variability models, such as feature or decision models [Kan+90;
SRG11], are popular techniques to handle variability, both in academia
[CAB11] and industry. Commercial tools for SPLE include G EARS [Kru07] and
P URE ::VARIANTS [Beu06].
Variability management is the key to taming the variability-induced complexity in product lines. It comprises activities such as variability modeling,
scoping, that is, controlling and restricting contributions, or maintaining variability information. Unfortunately, none of the mentioned publications or tools
address the need of a holistic view over variability implementations that span
both the configuration as well as the implementation space. My thesis fills this
gap in the context of Linux and system software that is built in a similar way.
2.2.2. Management of Variability in Operating Systems
Both, the general management of features and variability declaration, as well
as the design and implementation of customizable operating systems, are
well covered in the academic literature. The surveys published by Friedrich
et al., Denys, Piessens, and Matthijs and Tournier [Fri+01; DPM02; Tou05]
provide a good overview on the topic. Most recent work tends to focus on
how to implement dynamic adaptation and variability at run time. In the
context of this thesis, however, the focus lies on approaches for compile-time
customization.
16
2.2. Related Work
Figure 2.5.: The SPL approach at a Glance: “A software product line consists of a
set of intentional problem specifications (the problem space), a set of
extensional solution description, and a relation between both sets (the
solution space). Thereby each instance of the problem space (a specific problem) can be mapped to an instance of the solution space (the
specific solution). On the model level, the domain expert specifies the
variability of the problem space by a formal model of (abstract) features
and dependencies. The architect/developer implements this variability
(the architecture and implementation artifacts) in a solution space and
also provides a formal mapping from implementation elements to features (a configuration). This description is evaluated to derive the actual
implementation (the variant) for the application user.” [Loh09]
Unfortunately, the current state of the art for systematic management of
variability in the context of system software still leaves room for improvement
in prominent and relevant operating systems. While the literature does analyze
instances of SPL-like approaches (e.g., [Ber+10c; She+11]) in the context of
system software projects, such as the use of KCONFIG in Linux, or the configuration description language (CDL) in eCos, it turns out that the configuration
languages feature rather untypical characteristics compared to the synthetic
feature models commonly discussed in the literature [Ber+12a]. This greatly
limits automated reasoning on feature and variability models, which has been
well researched over the last decade [Whi+09; Whi+08; Ben+07; Ben+06;
BRCT05].
The situation of the implementation space (i.e., the actually implemented
17
Chapter 2. Problem Analysis and State of the Art
variability) is even more problematic, as the variation points are described
only implicitly in the source code. This also means that their dependencies and
constraints are usually, if at all, only informally declared (for instance in sourcecode comments). The integration of new, or the maintenance of old features,
requires the awareness of the constraints and requirements of the interaction
with other features [Kim+08]. Without proper design and documentation,
this can easily become a tedious and challenging task, which is a common
situation for many pieces of large and legacy software. The automatic reverse
engineering from source code, such as presented by She et al. [She+11], is
ongoing research that aims at the automatic identification and analysis of
feature implementations and their dependencies by feature model synthesis.
However, it turns out that this approach can only produce generalized feature
models [CW07], and leaves out the structure, that is, the ordering and nesting
of features and thus, greatly hinders the practical usefulness to developers for
maintenance on existing, and the integration of new features based on the
synthesized feature model.
An important and large problem that arises from the separate development
of the configuration and the implementation space is the lack of consistency
within and between these spaces. So far, the SPL community has focused
on such inconsistencies mainly only between the feature model and within a
single implementation language [Tha+07; CP06; Met+07], or only within the
feature model, that is, Level l0 [BRCT05; CW07; She+11]. Newer publications,
such as the work by Nadi and Holt [NH12], extend the analysis to also include
build system constraints. However, none of these works provide a holistic view
in the form of a variability model that would be able to address the questions
stated in Section 1.2.
2.2.3. Variability-Aware Analysis of Source Code
The independent development of the configuration and implementation space
causes many challenges for the reliable extraction of variability from source
code. System software and operating systems implement feature realizations
that are spread across makefiles, generators, CPP programs, C sources, linker
scripts, and more. This complicates the analysis, identification, and understanding of such feature implementations unnecessarily. However, especially
system software, which is well-known for its hard-to-debug and complex
software bugs (such as protocol violations, concurrency issues, etc.), greatly
benefits from static analysis, which allows identifying bugs in the source code
without actually having to run the code. This observation is supported by the
many works that have been published in the last years (e.g., [Jin+12; Pal+11;
Tan+07; Kre+06; LZ05; Ern+00; Eng+01] to name a few) on this topic.
The problem is that traditional tools for static analysis requires preprocessed
18
2.2. Related Work
source code, which means that they are only able to analyze a single, given
configuration.
Recent research on variability-aware parsing and type checking are promising
first steps to mitigate the challenge imposed by configurability. Variability
aware parsers include S UPER C by Gazzillo and Grimm [GG12] and T YPE C HEF
by Kästner et al. [Käs+11]. Both approaches work directly on the source
code with #ifdef annotations. Unlike traditional parsers, which produce a
token stream, the result of these approaches is a graph of tokens, in which
each forking node represents a variability choice. Hereby, each path in the
token graph represents a product variant that the preprocessor allows to
configure. Capturing the complete variability in a single data structure allows
the systematic analysis on all product variants simultaneously. In some ways,
it can be considered as a practical application of the choice calculus of Erwig
and Walkingshaw [EW11] on the C preprocessor.
Both approaches are able to generate test cases based on the implementation
in Linux. However, their practical use remains limited by the fact that not every
combination of #ifdef flags actually results in a valid configuration: The Linux
configuration model imposes a set of constraints that need to hold in addition
to what is specified in the code. For this reason, variability-aware parsers need
to be assisted with the constraints from the configuration and build system to
not produce configurations that cannot be configured in practice. Therefore, it
remains to be seen if and how those approaches can assist the automated bug
finding and fixing. In Section 4.2, I present an approach that constructs test
cases in the form of configurations that derive directly from this variability
and thus, does not expose this problem.
2.2.4. Implementation of Features in Operating Systems
While the previous sections have identified the lack of a holistic view on feature
implementations as a main cause of inconsistencies between the configuration
and the implementation space, one could wonder if this problem could have
been avoided by proper tool support. Despite the widely-known and often
criticized deficiencies regarding the lack of modularization and poor support
of features [SC92; EBN02], the CPP remains a dominant and widely used tool
for implementing variability in many relevant system software projects [Spi08;
Lie+10]. Several other programming paradigms have been proposed as an
alternative with first-class support for feature implementation, such as FeatureOriented Programming (FOP) [Bat04; MO04], or Aspect-Oriented Programming (AOP) [Kic+97; Loh09]. As the CiAO operating system [Loh+09] shows,
AOP makes it feasible to have both, variability declaration and implementation, in the same language. By this, the aspect compiler is able to ensure
consistency. Still, the implementation of optional and alternative features with
19
Chapter 2. Problem Analysis and State of the Art
CPP remains a very relevant and often used technique.
Especially in the context of system software programmed in C/C++, the
high coupling of feature implementations require the tangling on both, the
language, as well as the file level. As Liebig, Kästner, and Apel show, 16 percent of all #ifdef blocks are undisciplined, which means that the respective
feature implementations have a granularity below the statement level in the
C programming language [LKA11]. This greatly hinders the development of
variability-aware tools for analysis [Käs+11]. Nevertheless, practical experience shows that researchers continue to investigate the CPP as means for
implementing variability: For instance the work on virtual product lines by
Apel and Kästner [AK09] suggest to analyze, extract, and transform feature
modules only conceptually (i.e., virtual), and then use preprocessor-aware
backend-tools.
This is not enough for practical applications in operating systems because
of the non-centralized implementation (i.e., spread over different tools and
languages) of features. While the number of features in Linux has increased
linearly in the recent years from 2005 to 2010 [Lot+10], the number of #ifdef
blocks has only increased at half that speed [Tar+11b]. This indicates that in
practice, the Linux community implements variability not only with CPP, but
also with other means. It turns out that other programming paradigms (e.g.,
[Loh+06; Loh+09; Loh+11]) are largely avoided; instead, a multi-layered
and tangled mixture of C, CPP, Assembler, and MAKE, remain the dominant
means.
A possible explanation for the variety of possibilities to implement features
in operating systems could be that different features require different binding
times [CE00]. For instance, Linux offers both, loading drivers either statically
compiled into the kernel, or dynamically as loadable kernel module. For most
parts of Linux, compile-time mechanisms are clearly dominant. While the
exact causes are likely to be very project specific (and remain out of scope
of this thesis), the point here is that developers decide on the means for
implementing features purely on technical reasons, and also often choose
combinations of them. This results in features that have implementations
that spread across different files, #ifdef annotations, or makefiles. In many
cases, they are accompanied by generators that are tightly integrated into the
build process. The resulting distributed implementation of features seriously
impacts the maintenance as observed by Adams [Ada08].
Generative and model-driven approaches are a promising approach for
the description and generation of such non-homogeneous feature implementations, especially when used in combination with domain specific languages (DSLs) [VV11; Els+10]. Unfortunately, these methods either work
with a rather coarse-grained granularity or end up with too much complexity
in the generators for use in system software and operating systems.
20
2.2. Related Work
The Coccinelle-Project [Pad+08] is a novel approach for using DSLs in the
context of system software and operating systems. The used SmPL language
(semantic patch language) used by that working group could be used successfully in a number of studies [PLM10; Pal+11]. However, the focus lies on the
historical analysis of bug patterns and their causes in Linux on the C language
level; other implementation artifacts, such as makefiles or constraints from
the configuration system, remain out of scope of that work.
21
Chapter 2. Problem Analysis and State of the Art
2.3. Chapter Summary
For many of today’s systems software, which includes operating systems,
genericness regarding both, the hardware configuration and the user applications, has become an important requirement. Linux is no exception and
supports over twenty-five hardware architectures, hundreds of device drivers,
and classifies as general-purpose UNIX-like operating system. The enormous
amount of variability is managed by a dedicated configuration tool, KCONFIG,
which specifies the allowed kernel configurations (e.g., hardware platform,
included device drivers). The resulting kernel configuration then drives the
build process, which drives the build process and the C preprocessor in order
to construct the bootable Linux image and, if configured, LKMs.
In Linux this non-homogeneous management of feature implementations
leads to features that spread over different tools and languages. This is
error-prone, because without adequate tool support, a consistent feature
implementation needs to be checked manually. In practice this means that
the intensional variability declaration tends to diverge from the extensional
variability implementation.
To address this challenge, I propose to analyze both, the variability declaration and implementation, independently and cross-check the resulting models,
the configuration variability model ϕConfiguration and the implementation variability model ϕImplementation , as presented in this chapter, which is also the basis
for the applications presented in the following.
22
3
Extraction and Validation of Variability
Implementations
#ifdefs sprinkled all over the place are neither
an incentive for kernel developers to delve into
the code nor are they suitable for long-term
maintenance.
(Linux maintainer Thomas Gleixner in his
ECRTS ’10 keynote)
Complex software, and this includes system software such as operating
systems, often requires the management, implementation, and sometimes
also the reimplementation of modules for several product variants. Linux is
a typical example of a very large system software project that comes with
numerous feature implementations to achieve portability, optional drivers,
security features, and much more. The analysis in Section 2.1 shows that the
resulting variability is not cleanly modularized, but scattered across various
levels of variability, which causes not only consistency problems, but also
additional challenges for static analysis.
In this chapter, I describe the technical details of the variability extractors for
KCONFIG, K BUILD and CPP. For each of the three extraction tools, the result is
a propositional formula ϕ that encapsulates the configurability constraints in
a way that allows answering the questions stated in Section 1.2. The structure
of this chapter is as follows. In Section 3.1, I present an extractor for KCONFIG
that identifies the declared features and the variability that these features
express. It is followed by the description of the extractor for the Linux build
system in Section 3.2. Lastly, I present the extractor for CPP in Section 3.3.
23
Chapter 3. Extraction and Validation of Variability Implementations
Related Publications
The ideas and results of this chapter have partly also been published as:
[Tar+11b]
Reinhard Tartler, Daniel Lohmann, Julio Sincero, Wolfgang
Schröder-Preikschat. “Feature Consistency in Compile-TimeConfigurable System Software: Facing the Linux 10,000 Feature
Problem”. In: Proceedings of the ACM SIGOPS/EuroSys European
Conference on Computer Systems 2011 (EuroSys ’11). (Salzburg,
Austria). Edited by Christoph M. Kirsch and Gernot Heiser.
New York, NY, USA: ACM Press, Apr. 2011, pages 47–60. DOI:
10.1145/1966445.1966451
[Die+12a]
Christian Dietrich, Reinhard Tartler, Wolfgang SchröderPreikschat, Daniel Lohmann. “A Robust Approach for Variability Extraction from the Linux Build System”. In: Proceedings
of the 16th Software Product Line Conference (SPLC ’12). (Salvador, Brazil, Sept. 2–7, 2012). Edited by Eduardo Santana
de Almeida, Christa Schwanninger, and David Benavides. New
York, NY, USA: ACM Press, 2012, pages 21–30. DOI: 10.1145/
2362536.2362544
[Sin+10]
Julio Sincero, Reinhard Tartler, Daniel Lohmann, Wolfgang
Schröder-Preikschat. “Efficient Extraction and Analysis of
Preprocessor-Based Variability”. In: Proceedings of the 9th International Conference on Generative Programming and Component Engineering (GPCE ’10). (Eindhoven, The Netherlands).
Edited by Eelco Visser and Jaakko Järvi. New York, NY, USA:
ACM Press, 2010, pages 33–42. DOI: 10 . 1145 / 1868294 .
1868300
24
3.1. The Variability Language of the Linux Kernel
3.1. The Variability Language of the Linux Kernel
The KCONFIG configuration tool allows Linux developers to describe features
and their interdependencies in a dedicated language that was specifically
designed for this purpose. The KCONFIG language allows describing a variant
model that consists of features together with their constraints and dependencies. Both, the tool and the language, have been specifically designed and
implemented to support the development of the Linux kernel. Additionally, as
the empirical research of Berger et al. [Ber+12b] points out, the Linux configuration tool has also been adopted by a number of respectable open-source
projects.
Technically, KCONFIG is developed within the Linux kernel and thus, underlies its maintenance and review processes. Projects that adopt KCONFIG for
variability management copy the sources and integrate them into their own
build system. For purposes of variability analyses, the variability realization
remains practically identical among the mentioned projects. This allows applying the KCONFIG extractor on many very different software projects with
modest adaptation effort. As part of this thesis, I have successfully applied
my prototypical implementation for extracting the variability expressed by
KCONFIG on the software projects typeset in bold in Table 3.1.
The goal of the extractor that I present in this section is to distill the logical
constraints that stem from the feature interdependencies into a propositional
formula ϕKCONFIG . For the applications that I present in Chapter 4, there are
two basic requirements for the extracted variability model:
1. The variability model needs to be compatible with other variability constraints.
2. The variability model must allow efficient queries.
Project Name
Version
Features
Description
Linux
Freetz
Coreboot
Buildroot
EmbToolkit
Busybox
uClibc
Fiasco
axTLS
3.0
1.1.3
4.0
2010.11
0.1.0-rc12
1.18.0
0.9.31
2011081207
1.2.7
11,935
3,471
2,392
1,869
1,357
875
369
171
108
Operating System
Alternative Firmware for AVM-based Routers
Firmware Implementation for BIOS Based PCs
Cross-Compilation Helper Scripts
Build System Environment for Embedded Systems
Basic UNIX-like User-Space Utilities
A C Standard Library Implementation for Embedded Systems
Operating System
TLS Library Targeting Embedded Systems
Table 3.1.: Quantitative overview over software projects that employ KCONFIG for
variability management. Data partly taken from [Ber+12b].
25
Chapter 3. Extraction and Validation of Variability Implementations
config HOTPLUG_CPU
bool "Support for hot-pluggable CPUs"
depends on SMP && HOTPLUG && SYS_SUPPORTS_HOTPLUG_CPU
---help--Say Y here to allow turning CPUs off and on. CPUs can be
controlled through /sys/devices/system/cpu.
( Note: power management support will enable this option
automatically on SMP systems. )
Figure 3.1.: A simple, Boolean KCONFIG item
The first requirement stems from the observation that the technical implementation of variability in existing, economically relevant system software
happens with different languages, and thus, tools. As shown in Section 2.1.3
for Linux, the resulting hierarchy imposes variability constraints on one level
only in one direction. This not only applies to Linux, but is common for all
listed software projects listed in 3.1.
The second requirement is equally important. Due to the size of real models,
the resulting Boolean formulas become very large and complicated. For Linux,
the complete KCONFIG model for all over twenty-five architectures contains
more than 12,000 configuration options. Since the Boolean satisfiability problem is NP-complete, establishing huge propositional formulas, and solving
them in development tools, imposes the risk of intractable performance (i.e.,
run-time and memory) bottlenecks.
3.1.1. General Characteristics of the Linux Configuration Model
The KCONFIG configuration language is a domain specific language (DSL)
for describing configuration options in a menu structure that is very similar
to the hierarchical structure of feature models that are common in SPLE
(cf. Section 2.2.1). The syntax has evolved during the last ten years and the
Linux community nowadays considers it as a powerful and capable asset for
abstracting all the inherent complexities of the kernel configuration.
In Linux kernel version 3.0, a total of 782 KCONFIG files are employed,
consisting of 226,836 lines of code that describe 11,718 KCONFIG configuration
items and their dependencies. In addition, there are also 217 special items
(so called choice items) that are used to group the configuration items. Most
of the features in Linux are shared among all 23 architecture (not counting
Linux/um, a guest-level port in form of a user-space Linux application).
In this context, a KCONFIG feature is any user configurable item in the
KCONFIG frontend. They (usually) have a descriptive help text and declare
dependencies on other features. KCONFIG items can be one of the types int,
string, hex, boolean, tristate and choice. To illustrate, a simple feature of
26
3.1. The Variability Language of the Linux Kernel
type boolean is given in Figure 3.1.
Unsurprisingly, the largest variability occurs in the hardware abstraction
layer (HAL). Each architecture in Linux has its own “top-level” KCONFIG file
that references all other KCONFIG files with the source statement, which
works similar to the CPP #include statement. This allows kernel developers to
modularize the configuration space by specifying the same feature definition
across different architectures and place the feature implementation in the
same subdirectory. Technically, this means that Linux comes with more than
twenty distinct variability models, one for each architecture.
KCONFIG also offers with tristate an item type that is based on ternary
logic. Features of type tristate can have one of the states “not compiled”,
“statically linked into the kernel” or “compiled as loadable kernel module”. In
the latter case, the build scripts take care to compile and link the source files
that implement the feature into a kernel object (.ko) file that can be loaded
(and unloaded) at run time. Feature dependencies can declare constraints
on the exact state of tristate features, which need to be taken into account
during the translation of the KCONFIG model into propositional formulas.
3.1.2. Translation into Propositional Logic
In the literature, there are different reasoning techniques based on the formulas
extracted from the configuration space (e.g., [BRCT05; CW07]). As the next
chapter will show, being able to conduct such reasonings is essential to answer
the questions stated in Section 1.2. In all cases, the automated analysis of
variability requires a formal representation that supports automated reasoning.
Unfortunately, the authors of KCONFIG did only present the implementation
and a number of design goals [GR03], but do not provide a formal specification
of the KCONFIG language. Therefore, the variability has to be extracted with
reverse-engineering, that is, by studying the implementation.
I choose propositional logic as intermediate language, because in this representation, the variability is basically a set of propositional implications. Each
implication expresses what can be concluded from the fact that a feature
is selected in a configuration. This model format is similar to the KCONFIG
model representation used by Sincero [Sin13], and is very practical for the
integration into the tools that I have developed as part of this thesis.
The general idea to construct this model is to analyze the configuration
language syntax, and deduce logical implication from the given configuration model description. Hereby, each language construct results in one or
more implications In , which can be combined to the full variability model by
conjunction:
^
ϕKCONFIG :=
In | In all feature implications
n
27
Chapter 3. Extraction and Validation of Variability Implementations
The implementation of the extractor is done in two steps. The first step
uses a modified version of the original KCONFIG tool and simply serializes the
KCONFIG parse tree into a version-independent intermediate representation.
This tool is easily ported to different projects that use an older or different
version of the KCONFIG grammar. The next step operates on the basis of this
serialization and iterates over all KCONFIG features and generates the set of
implications In according to the rules.
In Appendix A, I elaborate on the rules that I have implemented for my
KCONFIG model in more detail. They are not inclusive; in fact, some language
features that were technically harder to implement, such as “symbol visibility”
and “default values”, are left unconsidered. While this may impact the accuracy
of the resulting model ϕKCONFIG to some degree, this approximated model is
useful enough to produce the results that I present in Chapter 4.
3.1.3. Analysis of the K CONFIG Model
In this subsection, I analyze a real-world feature model in more detail as an
example. For Linux/x86, version 3.0 there are 7,702 KCONFIG features with
7,339 dependencies. Table 3.2 shows more detailed statistics.
While the focus of this work are analyses with a holistic variability model,
the model ϕKCONFIG as constructed in Section 3.1.2 is already useful for some
reasoning techniques that are commonly applied in the literature. A basic
reasoning is the search for unselectable or short, dead features. Using the
extracted presence implication IF for a feature F , the following formula
allows a SAT-checker to determine, if the feature F is dead or alive:
(F ∧ IF ) ∨ (FMODULE ∧ IF )
F and F_MODULE are two configuration variables for the tristate feature F .
For this kind of features, the additional constraints explained in Section 3.1.2
apply. The formula consists of a disjunction of two parts. The first part of
the disjunction tests if the feature can be selected for inclusion in the Linux
bootable image. The second tests whether the feature can be compiled as LKM.
If the SAT checker is not able to find a solution, then the reasoning concludes
that the feature cannot be selected and has to be considered as dead on the
K CONFIG features
– boolean
– tristate
– other (int, hex, ...)
7702
2757
4700
245
dependencies
selects
choices
features in choices
7339
4105
57
201
Table 3.2.: KCONFIG features and constraints types in Linux/x86 version 3.0.
28
3.1. The Variability Language of the Linux Kernel
K CONFIG Features
7,514
– selectable (x86)
– unselectable (x86)
– selectable ( on some other architecture)
– unselectable (on any architecture)
5,994
1,520
1,445
75
Table 3.3.: Characteristics the translated KCONFIG model for Linux/x86 version 3.0.
given configuration model.
An analysis of Linux/x86 version 3.0 architecture model (Table 3.3) reveals
1,520 features that cannot be selected on Linux/x86. However, 1,445 of them
can be selected on some other architecture. This leaves 75 features that
cannot be selected on any architecture. Sixty of them are dead because
they depend on KCONFIG features that are not declared in any KCONFIG file
in the analyzed source tree. Technically, such dead features are of no use
in the configuration model and could in theory be removed to simplify the
KCONFIG dependencies. However, since Linux is developed by thousands of
developers in a very distributed manner, newly developed features are not
merged all at once. These evolutionary particularities have to be kept in mind
before reporting such dead features as bugs, and are best discussed with the
responsible maintainers. Nevertheless, the analysis of what features cannot be
enabled in any selection is useful for maintainers to raise awareness and help
making development decisions.
3.1.4. Related Work and Limitations
She and Berger [SB10] were the first to publish an attempt to capture the formal semantics of KCONFIG. Their technical report uses denotational semantics
to describe the variability effects of the KCONFIG constraints. Unfortunately,
it has not been validated or peer-reviewed by other researchers. Another
approach has been developed independently by Zengler and Küchlin [ZK10].
The work discusses a variety of uses for which a formalization of KCONFIG is
useful. However, combination with other models is not a design goal.
Both models aim at being exact. This means that the goal is producing a complete formula, from which for each configuration option, both, the presence
and the absence conditions of all features are clearly defined. This is mainly
an engineering challenge. Both research groups state in their publications that
compromises were necessary because of that, but cannot quantify the effects
on the resulting models. Thus, and from a practical point-of-view, the aim
remains unmet.
29
Chapter 3. Extraction and Validation of Variability Implementations
3.1.5. Summary: The Variability Language of the Linux Kernel
KCONFIG is a sophisticated language and configuration tool that allows the
users of Linux to configure over 10,000 features. It has been adopted for a
number of other open source projects, such as B USY B OX [Wel00] or C ORE BOOT [Bor09], and many more. This alone makes K CONFIG a very relevant
piece of configuration software for both practitioners and researchers.
The automated analysis of variability in Linux requires that the variability
expressed by KCONFIG is extracted in a form that can be processed with tools
without further manual interaction. For this, I choose propositional formulas,
which can be queried with a SAT checker. It turns out that the exact translation
of KCONFIG is not only very challenging, but also not strictly necessary for
many analyses. In this section, I have presented the most important logical
rules that are relevant for this thesis. While a more precise extraction would
in many cases lead to stronger analyses, the presented rules are sufficient for
implementing a prototype that supports the variability analyses discussed in
this thesis.
30
3.2. Robust Extraction of Variability from the Linux Build System
3.2. Robust Extraction of Variability from the Linux Build System
The complexity and maintenance costs of build systems are often underestimated. As McIntosh et al. point out, 4-27 percent of tasks involving
source code changes require an accompanied change in the related build
code [McI+11; Tam+12]. The conclusion of those studies confirms the work of
Adams [Ada08], namely that build scripts evolve with the software project and
are likely to have defects because of high development churn rates [McI+11]1 .
The bottom line is that the build system of large systems, such as Linux, evolves
at a very high pace, and the resulting challenges with the maintenance and
extension of such large-scale software projects call for tool support.
The primary task of a build system is to drive the tools for constructing the
resulting build products. In practice, many projects require the build system to
be both, portable and configurable. Portability means that the system can be
built with different compilers, linkers, etc. Configurability means that the build
system allows users to influence its behavior with defined variation points. The
current state of the art to handle both concerns is to implement abstractions
and means to hide the implementation details for both, the implementation in
the source code, and the build system.
Linux is both very portable as well as configurable. The portability aspect
is underlined by the more than twenty-five architectures for which Linux can
be compiled for. The configurability aspect is implemented in Linux with
KCONFIG, which has been discussed in detail in Section 3.1. The build system
K BUILD seamlessly integrates the configuration selection into the build process,
and is the key to Linux portability and configurability.
Translating the variability of build systems is challenging mostly for engineering reasons. Linux makefiles look quite different from typical text-book
makefiles as they employ K BUILD-specific idioms to implement Linux-specific
(variability) requirements. In practice, all existing variability extraction approaches that analyze makefiles exploit idioms that are specific to the analyzed
subject. The extractor that I present in this section is no exception to this.
3.2.1. The Role of the Build System
A crucial building block for analyzing the variability in Linux is a solid understanding of the actually implemented variability from their various sources
in form of a formal model. As can be seen in Figure 3.2, only a third of all
features do affect the work of the CPP, that is, have an effect on the sub-file
level. On the other hand, more than half of all features are referenced in the
1
McIntosh et al. define development churn as “[...] the rate of change in source code. Prior studies have
found that frequently changing source code, i.e., code with high churn, has a higher defect density
and causes more post-release defects.” [McI+11]
31
Chapter 3. Extraction and Validation of Variability Implementations
KCONFIG features
11,806 [100 %]
52.8 %
25.3 %
33.4 %
K BUILD interpreted
KCONFIG internal
CPP interpreted
6,236 [52.8 %]
2,987 [25.3 %]
3,946 [33.4 %]
41.3 %
11.5 %
11.5 %
21.9 %
K BUILD only
K BUILD/CPP
CPP only
4,873 [41.3 %]
1,363 [11.5 %]
2,583 [21.9 %]
Figure 3.2.: Distribution of variability points in Linux version 3.2 over all levels of
variability management techniques. It turns out that K BUILD implements
more than half of all variability points (52.8%), whereas only one third
of all variability points are handled by CPP. The percentages on the
edges indicate how the variability distributes over the child nodes. The
percentages inside the dark nodes sum up to 100%.
build system (K BUILD). These features select what files are included into the
build process. In essence, this means that variability is mostly implemented
in a coarse-grained manner in the makefiles. This underlines the need for a
precise and robust extractor of the implementation variability from the Linux
build system.
As detailed in Section 2.1, the user utilizes KCONFIG to customize the Linux
kernel configuration. Technically, this selection is converted into a file in
MAKE syntax that describes all selected features and their values. K BUILD then
resolves which file implements what feature, determines the set of translation
units that are relevant for a given configuration selection, and invokes the
compiler for each translation unit with configuration-dependent settings and
compilation flags. Internally, K BUILD employs GNU MAKE [SMS10] to control
the actual build process. In Linux v3.2 the mapping from features to translation
units is encoded in 1,568 makefiles that are spread across the source tree.
The huge amount of new patches that are integrated in every new release
makes Linux effectively a moving target [CKHM12]. Therefore, the extraction
of variability from Linux makefiles needs to be designed in a way that is robust
with respect to this fast pace of development. In particular, the extraction
approach should therefore not require any adaptations to the build system,
in particular not if the required changes require additional work with newer
version of the analyzed subject.
32
3.2. Robust Extraction of Variability from the Linux Build System
3.2.2. Related Approaches for Build System Variability Extraction
Before describing my approach for extracting the variability constraints from
makefiles, I revisit the related approaches for variability analysis from makefiles
that have been previously proposed.
Adams discusses the analysis and reverse-engineering of build systems in
his dissertation [Ada08] in detail, where he classifies tools for build-system
analysis as dynamic, if the analysis takes place during a compilation run.
Similarly, an analysis that does not require a compilation run classifies as
static. His makefile system reverse-engineering tool MAKAO uses both, buildtrace analysis and “static information such as build rules and unevaluated
patterns” [Ada+07a]. While the approach of the framework does scale up to
the size of Linux, it does not analyze the configurability aspects of Linux and
is not (directly) able to state the presence condition of a file in dependence of
a given KCONFIG configuration. Therefore, the tool cannot be repurposed for
my approach feasibly.
Tamrawi et al. present the tool SYM AKE [Tam+12], which allows the symbolic evaluation of MAKE rules. The result is a symbolic dependency graph that
represents build rules and dependencies among files via build commands. The
authors not only aim at fixing code smells and errors in the build scripts, but
also to support refactoring. SYM AKE promises an interesting new approach
for variability extraction from makefiles. However, because of the different
goals, the extraction of presence conditions, which are the essential result for
building the holistic variability model, is not directly possible.
The tool K BUILD M INER by Berger and She [Ber+10a] utilizes a so-called
“fuzzy parser” to transform the Linux makefiles into an abstract syntax tree
(AST), which is then transformed into presence conditions. The implementation consists of about 1,400 lines of Scala code and 450 lines of Java code.
I have downloaded the tool and the result set for Linux v2.6.33.3 from the
K BUILD M INER project website [BS]. Because the tool requires manual modification of existing makefiles (the author states that for Linux v2.6.28.6, 28
makefiles were adapted manually [Ber+10a]), it is not easily possible to apply
it to arbitrary versions of Linux.
Nadi and Holt have implemented another extractor for variability from
makefiles. Their implementation employs pattern matching that identifies
variability in K BUILD and consists of about 750 lines of Java code. In this thesis,
I compare my extractor to their implementation in the version that has been
used in one of their publications [NH12]. This version of the tool was kindly
provided by the original authors.
The last two approaches provide a good starting point for the extraction
of variability from the build system of system software such as Linux, and
have in fact heavily inspired the design of my approach. Nevertheless, neither
of the two approaches is particularly robust to evolutionary changes in the
33
Chapter 3. Extraction and Validation of Variability Implementations
development of Linux. Also, both are very specific to Linux. I present my
approach, which addresses both issues, in Section 3.2.5.
3.2.3. Common K BUILD Idioms
In the following, I elaborate with further details on idioms that implement the
K BUILD design requirements, as they are relevant for the automated variability
extraction.
Optional and Tristate Features and Dancing Makefiles
The K BUILD parts of the Linux makefiles use the MAKE language in a very
idiomatic way. In all makefile fragments, two variables collect selected and
unselected object files: The make variable obj-y contains the list of all files
that are to be compiled statically into the kernel. Similarly, the variable obj-m
collects all object files that will be compiled as LKM. Object files in the MAKE
variable obj-n are not considered for compilation. The suffixes {y,m,n} are
added by the expansion of variables from auto.conf.
The idea of this pattern dates back to 1997 and was proposed by Michael
Elizabeth Castain under the working title “Dancing Makefiles”2 . It was globally
integrated into the kernel makefiles by Linus Torvalds shortly before the
release of Linux v2.4. This pattern for managing variability with K BUILD is
best illustrated by a concrete example:
1
2
3
obj-y
+= fork.o
obj-$(CONFIG_SMP) += spinlock.o
obj-$(CONFIG_APM) += apm.o
In Line 1, the target fork.o is unconditionally added to the list obj-y, which
instructs K BUILD to compile and link the file directly into the kernel. In
Line 2, the variable CONFIG_SMP, which is taken from the KCONFIG selection,
controls the compilation of the target spinlock.o. Since the variable derives
from the feature KCONFIG SMP, which is declared as boolean, the source file
spinlock.o cannot be compiled as a LKM.
Consider the effects of the following KCONFIG configuration:
SMP=n
PM=y
APM=m
With this configuration, K BUILD adds the source file spinlock.o to the
variable obj-n, and skips it during the compilation process. In Line 3 the
file apm.o is handled in a similar way to spinlock.o. Because APM has the
value m, apm.o is added to obj-m and compiled as LKM.
2
https://lkml.org/lkml/1997/1/29/1
34
3.2. Robust Extraction of Variability from the Linux Build System
Note that instead of mentioning the source files, the makefile rules reference
only the resulting build products. The mapping to source files is implemented
by implicit rules (for details, cf. [SMS10, Chapter 10]). This mapping has to
be considered for any kind of makefile variability analysis.
Architecture and Cross-Compilation Support
Linux supports over twenty-five architectures, some of which lack the resources
to compile the kernel natively. In order to support such (mostly embedded)
platforms, K BUILD allows its users to cross-compile the kernel, that is, compile
on a different platform than it is actually executed. Unlike other projects that
employ KCONFIG such as F IASCO or C OREBOOT, in Linux the selection of a
target architecture is not part of the configuration. Instead, an environment
variable controls the target platform.
Loose Coupling
Programmers specify in K BUILD makefiles the conditions that lead to the
inclusion of source files in the compilation process. As shown above, this
commonly happens by mentioning the respective build products in the special
targets obj-y and obj-m. This works for the majority of cases, where a feature
is implemented by a single implementation file. However, in order to control
complete subsystems, which generally consist of several implementation files,
the programmer can also include subdirectories:
obj-$(CONFIG_PM)
+= power/
This line adds the subdirectory power conditionally, which makes K BUILD
recurse into the subdirectory depending on the selection of the feature PM
(power management). For each listed subdirectory, its containing makefile is
evaluated during the build process. This allows a more coarse-grained control
of source file compilation with KCONFIG configuration options.
3.2.4. Challenges in Build-System Analysis
In MAKE, the programmer has a large number of operations, including string
modifications, conditionals, and meta-programming, that he can use to implement variability. Even worse, MAKE also allows the execution of arbitrary
further programs ("shell escapes"). The Linux coding guidelines do not pose
any restrictions on what MAKE features should be used or avoided in K BUILD.
In this subsection, I present a few selected examples of constructs that are
present in the build system of Linux and are far more expressive than the
standard constructs.3
3
Note that most of the presented constructs are specific to
GNU / MAKE .
35
Chapter 3. Extraction and Validation of Variability Implementations
MAKE
Text Processing Functions
The following example, the source file arch/x86/kvm/Makefile in Linux v3.2,
uses the MAKE function addprefix:
obj-$(CONFIG_KVM_ASYNC_PF) += $(addprefix ../../../virt/kvm/,
async_pf.o)
The addprefix function takes an arbitrary amount of arguments, prefixes its
first argument to the remaining ones, and returns them. In this case using
addprefix is not really necessary, because there is only one additional argument and the whole expression is equal to ../../../virt/kvm/async_pf.o.
Nevertheless, this case requires special handling with a text-processing–based
approach.
Another common MAKE function is subst. The following example was taken
from net/l2tp/Makefile:
obj-$(subst y,$(CONFIG_L2TP),$(CONFIG_PPPOL2TP)) += l2tp_ppp.o
This example employs a substring replacement within a string with three
arguments: the search pattern, the replacement string, and the string that is to
be modified. In this example every occurrence of y in CONFIG_PPPOL2TP is
replaced with the value of CONFIG_L2TP. The features L2TP and PPPOL2TP
are marked as tristate (cf. Section A.1 on page 123) in KCONFIG, and
have one of the values y, m, n. This usage of subst ensures that the object
l2tp_ppp.o is only linked directly into the kernel (obj-y) if both features
are selected as m, and thus, compiled as loadable kernel module (LKM). If one
of the features is selected as module, it is added to obj-m.
Helper Variables
As a fully-featured programming language, MAKE offers diverse means to
organize the list obj-y and obj-m. Consider the following example taken from
the file drivers/video/matrox/Makefile:
my-obj-$(CONFIG_FB_MATROX_G) += matroxfb_crtc2.o
obj-$(CONFIG_FB_MATROX)
+= matroxfb_base.o $(my-obj-y)
obj-$(CONFIG_FB_MATROX_MAVEN) += matroxfb_maven.o
matroxfb_crtc2.o
The helper variable my-obj-y represents an indirection that is usually used
to improve readability.
Such constructs are easy to miss as they may be introduced (and removed)
at any time during the development of Linux. This makes such helper variables
another subtle source of bugs for parsing-based approaches.
36
3.2. Robust Extraction of Variability from the Linux Build System
Feature
eval
foreach
subst
addprefix
shell
Occurrences
3
16
57
88
127
Table 3.4.: Occurrences of selected language features in the makefiles of Linux 3.2
Shell Escapes
Most problematic is the shell function, which allows programmers to spawn
an arbitrary external program and let it control (parts of) the compilation
process. A parsing-based approach is not able to handle such constructs by
design.
Generative Programming
In K BUILD, programmers also use generative programming techniques and
loop constructs, like in this excerpt taken from arch/ia64/kernel/Makefile:
ASM_PARAVIRT_OBJS = ivt.o entry.o fsys.o
define paravirtualized_native
AFLAGS_$(1) += -D__IA64_ASM_PARAVIRTUALIZED_NATIVE
[...]
extra-y += pvchk-$(1)
endef
$(foreach obj,$(ASM_PARAVIRT_OBJS),$(eval $(call
paravirtualized_native,$(obj))))
Here, a list of implementation files (ivt.S, entry.S and fsys.S) not only needs
to be included, but also require special compilation flags. In this example,
the macro paravirtualized_native is evaluated for all three implementation
files by the MAKE tool at compilation-time. Again, for a text-processing–based
approach, this corner case is challenging to implement in a general manner.
Summary
Both competing text-processing–based approaches [NH12; Ber+10a] fail on
the examples shown above. This does not invalidate their approaches because,
luckily, such language features are currently not used very frequently in K BUILD.
As the quantification in Table 3.4 shows, problematic languages are used, and
their usage is not discouraged by Linux coding guidelines. On the longer term,
this implies a danger regarding the robustness of text processing as a means
to extract variability information from the Linux build system.
37
Chapter 3. Extraction and Validation of Variability Implementations
3.2.5. Experimental Probing for Build-System Variability
The goal of the variability extraction process is to establish the propositional
formula ϕK BUILD , which allows determining what configuration selection influence what variation points in what way. In the context of build-system
variability, this means to identify what configuration leads to the compilation
of which source files. Conceptually, this is described with the logical conjunction of all presence conditions for every source artifact f (in the case of Linux,
source files) in the set of all files in the codebase F:
^
ϕK BUILD :=
PC (f ) | F : all source files
(3.1)
f ∈F
In order to avoid the difficulties described in Section 3.2.4, my probing-based
extraction approach (partially) executes the build system with different feature
selections and observes the behavioral changes, that is, changes in the set of
compiled source files. Then, the tool determines the presence implication of
a source file by comparing what feature selections lead to the inclusion of
what files during the compilation. The formula ϕK BUILD is then constructed by
conjuncting all observed implications. I have implemented the approach in
form of a prototype tool named GOLEM, which operates on source trees that
use the Linux build system K BUILD. The rest of this subsection discusses the
basic operations that are implemented in GOLEM, and may be skipped on the
first reading.
3.2.5.1. Basic idea
Kbuild
compiled files
Foutput
Kconfig
selection
Sinput
Instead of parsing the makefile, the GOLEM approach is based on “clever
probing”. The general principle of operation is depicted in Figure 3.3. Basically,
systematically constructed synthetic configurations are passed to the build
system, represented as the gray box in the figure. Then, the approach notes
for each feature what files the build system would have built.
The approach starts with the minimal feature selection Sbase , which compiles
the set of files Fbase during the compilation process. Then, GOLEM adds an ad-
Figure 3.3.: Abstract view on the variability implemented by K BUILD
38
3.2. Robust Extraction of Variability from the Linux Build System
Build system as a gray box
FILE3
DIR3
B2
DIR2
B1
DIR1
B3
FILE2
A2
FILE0
DIR0
A1
Compiled Source Files
Feature Selection
FILE1
Figure 3.4.: An abstract model of a hierarchic build system with subdirectories (DIRn )
and conditionally compiled source files (FILEn ). Here DIR0 is the starting
point.
ditional feature f1 to the set Fbase . The new feature selection S1 := Fbase + {f1 }
now compiles the set of files F1 . For every file that is in F1 , but not in Fbase ,
GOLEM has found a feature selection that enables this particular file. Conducting this for all possible feature selections allows observing experimentally the
complete variability of the build system.
A naïve implementation of this approach would have to probe 2n set of files
for n features in the build system. This is not acceptable for sophisticated
software-projects such as Linux, which come with over 5,000 features. The
trick is therefore to not consider the build system K BUILD as a black box, but
to exploit the idioms that implement the variability mechanics in Linux, like
the “dancing makefiles” pattern as sketched in Section 3.2.3.
3.2.5.2. From Feature Selections to File Sets
Figure 3.4 depicts the conceptual internal working of the build system, which
the GOLEM approach exploits. The approach translates the organization of
build products and directories into a directed acyclic graph (DAC) that has two
types of nodes representing source files and directories. Every build product
(the result of the compilation of a source file, usually an object file) is assigned
a file node, which is always a leaf node. Each subdirectory is assigned a
39
Chapter 3. Extraction and Validation of Variability Implementations
directory node, from which other directory or file nodes grow. The organization
of the DAC represents the organization of the source tree.
Note that in general, a KCONFIG feature selection does not necessarily
traverse all subdirectories in the Linux source tree at compilation time. This is
because subdirectories are not only used to organize files, but programmers
use subdirectories also as a building block for implementing build-system
variability. For instance, the sound/ subdirectory in the top-level of the Linux
sources, which holds the implementation of the Linux sound subsystem, is
only traversed if the user enables the configuration option CONFIG_SOUND.
The variability of the system is encoded by the constraints under which a
source file is compiled. These constraints might be complex expressions or
fairly simple tests whether a certain source file (a file node) is selected or
not. The presented approach works best for simple single-feature tests, but
is, as described later, straightforward to extend to work on more complex
expressions.
In Linux, the architecture-specific top-level K BUILD file is the entry point for
the build system. All variability extraction approaches start from there and
choose the subdirectories to traverse based on the feature selection. This may
not necessarily hold for other systems. If there is more than one top-level
directory node, the process has to be started for all these top-level directory
nodes.
For each subdirectory that appears in the file sets Fn+1 , the condition under
which the compilation process traverses it plays an important role: If the
condition is non-trivial, then it is taken as precondition (the base expression)
to all presence conditions of its included files. After processing all files in the
file set Fn , each of the included subdirectories is processed recursively and the
base expression is conjuncted to all found constraints.
The list operation is the basic primitive operation that finds the file set and
all considered subdirectories that are associated to a feature selection:
list : Selection 7−→ (FileSet, SubDirs)
(3.2)
Every build system has to implement this primitive at least conceptually.
However, it is often not explicitly exposed, and thus, hard to access in practice.
There are several options how the list operation can be implemented for a
given build system. One option is to actually apply the given configuration,
start the compilation process and extract the mapping from build traces (as
done in the work of Adams et al. [Ada+07b]). However, because of very timeconsuming compilation phases this approach is not feasible for large-scale
software projects such as Linux. Therefore, an efficient implementation of this
mapping is essential.
My implementation of the list operation for Linux drives K BUILD in a way
that does not actually compile any source file. GOLEM traverses the source
40
3.2. Robust Extraction of Variability from the Linux Build System
tree in the same way as the regular compilation process and, following the
“dancing makefile pattern” (cf. Section 3.2.3), collects all selected files and the
visited subdirectories.4 The implementation employs the respective K BUILD
internals to ensure an accurate operation of the list primitive.
To demonstrate the list operation in practice, consider the system modeled
in Figure 3.4. With all features deselected, only the file FILE0 is compiled:
list(¬A1 ∧ ¬A2 ∧ ¬B1 ∧ ¬B2 ∧ ¬B3 ) = ({FILE0 }, {DIR0 })
The file node FILE0 does not have any expressions on the path from the toplevel directory node, because it is unconditionally compiled. When enabling
only the feature A1 , the file FILE1 gets additionally compiled:
list(A1 ∧ ¬A2 ∧ ¬B1 ∧ ¬B2 ∧ ¬B3 ) = ({FILE0 , FILE1 }, {DIR0 })
In this case, all files in the top-level directory are included in the compilation
process. In order to traverse into the subdirectory DIR1 , the feature A2 is
required. In this case, the files FILE0 and FILE3 are compiled:
list(¬A1 ∧ A2 ∧ ¬B1 ∧ ¬B2 ∧ ¬B3 ) = ({FILE0 , FILE3 }, {DIR0 , DIR1 })
3.2.5.3. Collecting Expressions
The configuration-dependent inclusion of subdirectories (represented by the
inclusion of directory nodes) and source-code files (represented by file nodes)
during the compilation process is controlled by a propositional expression consisting of configuration switches. The (recursive) exploration of the hierarchic
structure requires finding all these expressions in the build system. For build
systems with n configuration switches, this would in theory require 2n tests
on each recursion step, which is not practical. In practice, the search space
can be dramatically narrowed by only testing those configuration switches
that are actually referenced on each directory. This observation leads to the
next conceptional basic operation EXPRESSION _ IN _ DIR, which takes a directory
node and maps it to a set of all possible expressions that reference another
directory node or file node:
DIR 7−→ {Expression1 , Expression2 , . . . , Expressionn }
(3.3)
GOLEM implements EXPRESSION _ IN _ DIR as follows: In each recursion step,
locate all expressions in the makefile for the current directory node that start
EXPRESSION _ IN _ DIR :
4
Technically the
MAKE
variables obj-y and obj-m are used internally to drive the compilation.
41
Chapter 3. Extraction and Validation of Variability Implementations
with CONFIG_ and reference a declared feature. The EXPRESSION _ IN _ DIR function is straightforward to implement with regular expressions that extract all
referenced variables in the makefile that start with CONFIG_.5 In some ways,
this is also some sort of text processing, but in contrast to the competing,
parsing-based approaches [Ber+10a; NH12], the EXPRESSION _ IN _ DIR identifies only references to feature identifiers and not their (context-dependent)
semantics.
As an example of the EXPRESSION _ IN _ DIR operation some mappings of the
system modeled in Figure 3.4 are:
=
{A1 , A2 }
EXPRESSION _ IN _ DIR (DIR1 ) = {B1 , B2 , B3 }
EXPRESSION _ IN _ DIR (DIR0 )
3.2.5.4. Base Selection and Added Features
The algorithm starts with the empty selection S∅ as starting point for the recursion, which serves as base point for the file set and subdirectory differences.
The empty selection contains no selected feature at all. This base file set only
includes files that are included in every configuration. One example of such a
file in Linux is the file kernel/fork.c. It is essential for the process creation
and therefore needed in every configuration.
(Fbase , Dbase ) := list(S∅ )
(3.4)
The files Fbase selected by S∅ are the files that are unconditionally compiled.
S∅ also selects the subdirectories Dbase , which are the starting point for the
build system during the source tree traversal.
The presented approach uses Fbase and Dbase in the same manner as starting
point. In Linux/x86 v3.2, GOLEM detects 334 such unconditional files.
3.2.5.5. Build-System Probing
The implementation of the build system probing makes extensive use of
the basic operations list and EXPRESSION _ IN _ DIR: It recursively traverses all
directory nodes (i.e., subdirectories) and applies all found expressions found
by EXPRESSION _ IN _ DIR to the list operation. For each file in the result set,
the feature selection that enables its compilation is inserted in a global map
VPPC . By this, the map VPPC represents the mapping of file nodes to a list
of configuration selections, which, if selected in a configuration, lead to the
5
This may result in accidentally included configuration switches that are referenced for example in
comments and similar. This, however, leads to a few additional, unnecessary probing steps that do
not invalidate the approach, but “only” slow down the execution.
42
3.2. Robust Extraction of Variability from the Linux Build System
compilation of the file node. Existing entries in the map VPPC are appended, so
that the result is a list of feature selections:
VPPC : S(FILE) 7−→ {Selection1 , Selection2 , . . . , Selectionn }
The map VPPC allows the construction of the presence condition PC (FILE)
for each file node, that is, implementation source files, by calculating the
disjunction of all feature selections that enable their compilation:
_
PC (VP) ↔
s | s ∈ S(FILE), K : some feature selection
(3.5)
s∈K
The presence condition PC (f ) for the file f may be a tautology (i.e., true)
if the file is unconditionally compiled, a contradiction (i.e., false) if the file
is never compiled, or a propositional condition that indicates what features
need to be enabled in order to compile the source file. Equation 3.5 allows the
construction of the propositional formula ϕK BUILD that describes the variability
of the build system as described in Equation 3.1 on page 38.
In Appendix C, I present additional details on the implementation of GOLEM.
This includes a more detailed description of the implemented algorithms and
an exemplary execution example in Appendix C.3.
3.2.6. Benefits and Limitations
Compared to the parsing-based approaches for extracting variability from the
build system presented in Section 3.2.2, build-system probing with the GOLEM
tool exhibits a number of unique characteristics. While the existing parsingbased approaches suffer from technical implementation challenges that require
manual (and error-prone) engineering for the many corner-cases, complicated
makefile constructions as presented in Section 3.2.4, and invocation of external
tools in the build system, in my approach all these challenges are handled
error-free. It is also much harder for a parsing based approach to keep pace
with the Linux development. The approach implemented by the GOLEM tool
on the other hand works predictably for a wide range of Linux versions and
architectures.
The build-system probing approach makes the following assumptions on the
build system:
1. File presence implications in K BUILD correspond to the hierarchical organization of directories along subsystems. This means that if a feature
is a prerequisite for enabling files in a subdirectory, then this constraint
applies for each file in that directory.
2. In all subdirectories, each file is only dependent on a single feature and
not by a conjunction of two or more features.
43
Chapter 3. Extraction and Validation of Variability Implementations
3. A feature always selects additional source files for compilation. In no
case the selection of a feature causes a source file to be removed from
compilation process.
For variation points in build-systems that violate these assumptions, GOLEM
misses presence implications, which means that the bi-implication in Equation 3.5 becomes a simple implication. This can be easily addressed by additional engineering, namely implementing special handling for those corner
cases, similarly as implemented in the parsing-based approaches. In this thesis,
I apply GOLEM on three system-software related software projects in order to
derive the build-system variability. Luckily for none of these projects, I had
to implement such special handling because in all of these systems, the three
assumptions hold sufficiently well. However, I expect that additional engineering would significantly improve the result of the extraction processes even
further. This means that the approach remains valid and very applicable to
many real-world software projects, which is critical for the variability analyses
from Chapter 4.
3.2.7. Application to Real-World Software Projects
In order to demonstrate the general applicability of the probing-based makefile
variability extraction approach, I apply the GOLEM tool to three software
projects that feature quite different sets of makefiles: Linux, B USY B OX and
F IASCO.
3.2.7.1. Application to Linux and Comparison with Parsing-Based Approaches
I have implemented the probing-based approach in form of the prototype tool
which encompasses about 1,000 lines of Python code. The key for
the robust extraction of build-system variability is to implement the K BUILD
specific probing primitives in two additional “front-end” makefiles, which
encompass about 120 additional lines of MAKE code. By this, not a single line
in Linux had to be changed for the analysis.
For Linux, there are existing implementations for variability extraction
available that are able to identify the variability constraints from K BUILD.
This allows me to evaluate the performance, robustness, and coverage of my
approach and implementation. I compare my prototypical implementation
with K BUILD M INER [Ber+10b] and the extractor by Nadi and Holt [NH11]
regarding the aspects run time, robustness, and coverage.
GOLEM ,
44
3.2. Robust Extraction of Variability from the Linux Build System
All source files for v2.6.25 (without #included files)
Files hit by K BUILD M INER
Files hit by GOLEM
Files hit by Nadi parser
6,826 (127)
data not available
6,274 (93.7%)
tool crashes
All source files for v2.6.28.6 (without #included files)
Files hit by K BUILD M INER
Files hit by GOLEM tool
Files hit by Nadi parser
7,665 (153)
7,243 (96.4%)
7,032 (93.6%)
All source files for v2.6.33.3 (without #included files)
Files hit by K BUILD M INER
Files hit by GOLEM
Files hit by Nadi parser
9,827 (261)
9,090 (95%)
9,079 (94.9%)
7,154 (74.8%)
All source files for v2.6.37 (without #included files)
Files hit by K BUILD M INER
Files hit by GOLEM
Files hit by Nadi parser
(292)
data not available
10,145 (95.1%)
7,916 (74.2%)
All source files for v3.2 (without #included files)
Files hit by K BUILD M INER
Files hit by GOLEM
Files hit by Nadi parser
(276)
data not available
11,050 (95.4%)
8,592 (74.2%)
tool crashes
10,958
11,862
Table 3.5.: Direct quantitative comparison over Linux versions over the last 5 years.
The kernel versions are roughly equidistant over the time and include all
versions for which data sets are available.
Performance
Both parsing-based approaches are (presumably) much faster than the GOLEM
implementation presented in this thesis. For K BUILD M INER [Ber+10a], no
run-time data is available. The parser by Nadi and Holt [NH12] processes a
single architecture in under 30 seconds. The current GOLEM implementation
takes approximately 20 minutes per architecture on a standard quad-core
Intel i7 workstation. The obvious bottleneck is the run time and the amount
of probing steps. For Linux/x86 v3.2, the list operation takes about a second
(the exact run time depends on the selected features and filesystem cache
state) and is executed 7,073 times.
Robustness
Recent versions of Linux integrate more than ten thousand of changes, with
an increasing trend. [CKHM12] The (meaningful) evolutionary analysis of
variability therefore requires an approach that is both conceptually as well as
implementation-wise robust with respect to such development changes. For
this reason, an important design goal is to be inured to evolutionary changes
in the build system. I evaluate this property by applying the GOLEM tool to
45
Chapter 3. Extraction and Validation of Variability Implementations
five Linux releases with one year distance that cover 4 years of the Linux
development (2008-2012).6 Table 3.5 summarizes the results of this analysis.
In general, it turns out that it is challenging to apply the two parsing-based
approaches to Linux versions that the authors of the variability extraction tools
have not considered during the development. For the approach presented
by Berger et al. [Ber+10a], which relies on “fuzzy-parsing”, there are only
data sets for Linux version v2.6.28.6 [Ber+10a] and v2.6.33.3 [BS] available.
All other versions require changes of the Linux makefiles that are neither
documented nor is it obvious how to apply them to other versions of Linux:
The modifications include the disabling of parts of arch/x86/Makefile in
a way that break a regular compilation. The technical report leaves it open
what effects these changes have on the extracted logical constraints.7
The GOLEM tool produces presence implications on all selected versions
without requiring any source code modification or version specific adaptations.
Also, the extraction process for the 22 other architectures in Linux v3.2 did not
require any further modification.
Both parsing-based approaches have difficulties to achieve a robust operation
on a wide range of versions. Since the Linux build system is still in active
development and difficulties like those described in Section 3.2.4 may appear
with every new version, every newly introduced MAKE idiom requires manual
(and thus error-prone) additional engineering in order to keep up with the
Linux development. In contrast to that, my GOLEM tool works in a robust
manner with stable results for each version without any further adaptations.
Coverage
In order to compare the three K BUILD variability extractors quantitatively, I
analyze for how many source files the respective approach produces a logical
formula as metric for their coverage in the Linux v2.6.33.3 source tree. This
source tree is the most recent version of Linux for which results of all tools are
available.
For that version, K BUILD handles a total of 9,827 source files. As pointed out
by Nadi and Holt [NH11], 276 of these source files (2.8%) are referenced by
#include-statements in other implementation source files rather than K BUILD
rules in K BUILD.
6
In order to keep the results for the various implementations comparable, I refrain from analyzing
earlier versions than Linux v2.6.25, because the arch-x86 architecture was introduced in v2.6.24
by merging the 32bit and 64bit variants, which were previously maintained separately.
7
The parsing approach presented by Nadi and Holt [NH12] does not require any modifications
to existing Makefiles. I was able to produce presence implications for two additional versions.
Unfortunately, the tool crashes with an endless recursion and a stack overflow on Linux v2.6.28.6
and earlier, so that no logical constraints could be obtained.
46
3.2. Robust Extraction of Variability from the Linux Build System
The variability extractor by Nadi and Holt [NH12] identifies presence implications for 7,154 out of all source files (74.8%). For 2,412 source files, the
tool was unable to find any logical implication. A quick analysis of the data
indicates that deficiencies in the mapping from build products to source files
(cf. Section 3.2.3) are part of the problem for this relatively large number.
An analysis of the data for K BUILD M INER for Linux/x86 shows that the
tool produces presence implications for 9,090 out of all source files (95%) on
Linux/x86 v2.6.33.3. This data is consistent to the technical report [Ber+10a],
which states a coverage of 94 percent on Linux/x86 v2.6.28.6.
The current implementation of the GOLEM tool calculates presence implications for 9,079 out of the 9,566 source files on Linux v2.6.33.3 (94.9%) on
Linux/x86.
In addition to this quantitative analysis, the GOLEM prototype implementation also compares qualitatively to K BUILD M INER. The qualitative comparison
can be found in Appendix C.3.
3.2.7.2. Application on B USY B OX
B USY B OX [Bus] is a software product that provides many traditional U NIX
tools from (/usr)/(s)bin for embedded systems. All tools are assembled
into a single executable in order to save system resources such as flash space
or memory consumption. Most provided tools, and their functionality, can also
be configured at compile time.
The B USY B OX developers have adopted the Linux configuration system
KCONFIG and the build system K BUILD. However, as both KCONFIG and K BUILD
is developed as part of the Linux kernel, the B USY B OX developers use adapted,
and sometimes a bit outdated, versions of the pendants in Linux. Fortunately,
these differences were purely technical, so that the tools that I present in this
thesis can be adapted to B USY B OX with reasonable effort.
Adapting GOLEM to the B USY B OX Build-System
Applying GOLEM to B USY B OX is straightforward, because B USY B OX also employs KCONFIG as configurator and a modified version of K BUILD as build
system. Therefore, the build system primitives from Section 3.2.5.4 could be
reused with little modification: Only 24 additional lines of source code were
necessary to support B USY B OX and have been integrated into my development
prototype.
Results
Calculating ϕK BUILD for B USY B OX 1.20, which consists of 710 source files, takes
less than a minute on a modest quad-core Intel-based workstation. However
not all files are handled by the build system. A number of the not handled
source files are helper scripts for the configuration and build process. Not
47
Chapter 3. Extraction and Validation of Variability Implementations
all .c files
– helper scripts
– explicit unused
– implicit unused
– #include-ed
– example code
P
variation points
covered by the approach
files
(of all .c files )
710
26
115
8
22
5
100%
(3.7%)
(16.2%)
(1.1%)
(3.1%)
(0.7%)
534
534
(75.2%)
(75.2%)
Table 3.6.: Breakdown of source files in B USY B OX
counting such “inactive” files, 534 source files remain. For these remaining files,
GOLEM calculates presence implications for every file. Table 3.6 summarizes
the results.
3.2.7.3. Application on the Fiasco µ-Kernel
The F IASCO [Fia] operating system is an L4-based µ-kernel system that is developed at the TU Dresden as part of TUDO:OS. The µ-kernel supports various
hardware architectures, including x86, ARM and PowerPC. Additionally, it can
also run as user-level process on top of an ordinary x86 Linux system. Similar
to Linux, the compile-time–configured system variability comprises both, the
supported hardware architectures, and software variability.
Adapting GOLEM to the F IASCO Build System
F IASCO imports the Linux KCONFIG configurator for managing the declaration
of its 132 features into its custom makefile system. F IASCO does not employ
any tristate features; in the µ-kernel design, all loadable functionality run in
user-space server processes, which eliminates the need for LKMs. Therefore,
F IASCO does not declare any configuration items with the type tristate. Out
of all features in F IASCO, 111 features are of type boolean, 20 are of type
string and one is of type integer.
F IASCO uses a makefile system that does not use the structure known from
the Linux kernel. Nevertheless, the robust design and implementation of the
probing-based approach of the GOLEM tool was still straightforward to apply
to F IASCO. The rest of this subsection sketches the challenges and the required
adaptations.
Unlike Linux, the fine-grained variability implementation only partly employs the C preprocessor. The majority of variation points are implemented
using the Hohmuth preprocessor [Hoh05]. The Hohmuth preprocessor is a
C++-semantics aware preprocessor that implements automatic header gen-
48
3.2. Robust Extraction of Variability from the Linux Build System
User Selection
Ê
Kconfig
Ë converts
selection to
Ì is read by
auto.make
make
drives
source code
make and
modules files
Hohmuth preprocess
Í transforms to
preprocessed source code
CPP
gcc
Î compiles
Compiled
Fiasco Variant
Figure 3.5.: The toolchain for building the F IASCO µ-Kernel
eration, automatic inlining of functions, and controls which source modules
to compile. Its author describes it as a “tool [which enables you] to write
unit-style single-source-file modules in C++” [Hoh05]. In essence, the KCONFIG
configuration controls which source modules to compile by special “Hohmuth
flags”. Technically source modules are described in makefiles, which employ
the “dancing makefiles” (cf. Section 3.2.3) idiom that is also found in Linux.
Figure 3.5 visualizes the involved tools that act during the construction of
a bootable F IASCO kernel image. In that figure, source artifacts are depicted
in blue and tools in red. On the intentional side, features are configured
centrally with the KCONFIG configuration tool in Step Ê. Then, KCONFIG saves
the configuration into the source artifact auto.make, which encapsulates the
configuration in MAKE syntax (Step Ë) and drives the compilation process
(Step Ì). Unlike in Linux, there is no clear distinction between fine-grained and
coarse-grained variability implementation on the extensional side. However,
because the Hohmuth preprocessor transforms all source files to CPP, the
variability analyses in this thesis focus on the generated source files in Step Í.
Finally, the build-system drives GCC (and thus, CPP) to construct a bootable
F IASCO kernel in Step Î.
The bottom line is that the technical realization of variability is interwoven
very tightly with the build system and the Hohmuth preprocessor. In contrast
to Linux, the distinction between coarse-grained and fine-grained variability
becomes blurred, which in fact, makes it easier to conduct the variability
analysis on the whole toolchain.
Results
The adaptation of the GOLEM variability extractor for the F IASCO build system
involves the implementation of the helper functions list (Equation 3.2) and
EXPRESSION _ IN _ DIR (Equation 3.3). In F IASCO , the Modules files, which
49
Chapter 3. Extraction and Validation of Variability Implementations
use the MAKE syntax, represent the directory nodes, and the Hohmuth flags
represent the controlled file nodes. In total, the adaption for F IASCO took only
126 additional lines of code to GOLEM.
The results can be summarized as follows: With GOLEM, 98 presence implications for 98 Hohmuth flags can be inferred. All found presence implications
were verified manually and reflect the intended variability.
3.2.8. Summary: Robust Extraction of Variability from the Linux Build System
The build system contributes a significant portion to the implemented variability in a software system. This is the case in particular for Linux, where the
build system K BUILD references more than half of all configuration switches
that are declared in KCONFIG. For many variability analyses, it is therefore
inevitable to have a sufficiently accurate extraction of variability from the
build system available.
Unfortunately, writing appropriate extraction tools is a very challenging task.
For one, makefiles allow programmers to specify variation points in many
different ways, including the execution of external commands. This makes
parsing makefiles very challenging and error-prone. An additional challenge
is the high rate of development to the Linux build system, which requires
frequent adaptations in the parser. Revisiting the literature, no existing tool is
able to address these problems.
In this subsection, I have presented a probing-based approach that avoids
both problems. By executing the build system in a controlled way, the GOLEM
prototype analyzes the particularities of the makefiles very accurately. Moreover, GOLEM works on all modern Linux versions, B USY B OX and F IASCO,
without needing any adaptation to the analyzed subject and with an acceptable performance. As the case studies in Chapter 4 will show, the accuracy of
the current prototype is more than sufficient.
50
3.3. Variability with the C Preprocessor
3.3. Variability with the C Preprocessor
This section describes an approach to identify the CPP-induced conditional
compilation from a source file, and how to translate the extracted variability
into a propositional formula ϕCPP . The result is compatible with the variability
models extracted from Section 3.1 and Section 3.2.
3.3.1. The use of CPP in Linux
The language of the C preprocessor is defined in § 6.10 of the C99 language
specification [ISO99]. This makes the CPP an integral part of the C language,
which has lead to the very wide adoption for all software written in C. The
C preprocessor (CPP) is a tool that allows programmers to insert source files
(via the #include command), to define shortcuts in form of macros (via the
#define command), and most importantly, to implement variability by means
of conditional compilation. This provides programmers a great flexibility with
a simple to use language. Its use (and misuse) has already been explained
extensively in the literature, most prominently in the work of Spencer and
Collyer [SC92]. That work discusses the experiences the authors have made
with solving the portability challenges in “C News”, a piece of software for
participating in the usenet. The paper concludes:
“In our experience, #ifdef is usually a bad idea (although we do use
it in places). Its legitimate uses are fairly narrow, and it gets abused
almost as badly as the notorious goto statement. Like the goto, the
#ifdef often degrades modularity and readability (intentionally or
not). Given some advance planning, there are better ways to be
portable.”
However, in 20 years since this publication, exploratory studies such as the one
conducted by Liebig et al. show that CPP remains the most dominant tool for
variability management in System Software [Lie+10]. This does not mean that
the criticism of Spencer and Collyer was wrong or remained unheard. Rather,
we observe that the legitimate uses of CPP have been identified, understood
and integrated into the coding guidelines. For instance, the patch submission
instructions in Linux8 give very explicit guidelines on the use of #ifdef:
“#ifdef [statements] are ugly
Code cluttered with ifdefs is difficult to read and maintain. Don’t
do it. Instead, put your ifdefs in a header, and conditionally define
8
The Linux coding guidelines are found in the file Documentation/SubmissionChecklist in the
Linux source tree.
51
Chapter 3. Extraction and Validation of Variability Implementations
Directive
Description
#if EXPR
defined IDENT
include the following block if EXPR evaluates to true
check whether the CPP flag IDENT has been defined with the
#define directive
abbreviated form for #if defined IDENT
abbreviated form for #if !defined IDENT
alternative block if the preceding block is not included
conditional inclusion on the following block if EXPR evaluates to
true and the preceding block is not included
alternative block if the preceding block is not included
terminate an conditional included block
assign an identifier an entry in the symbol table
removes an identifier from the symbol table
#ifdef IDENT
#ifndef IDENT
#else
#elif EXPR
#else
#endif
#define IDENT
#undef IDENT
Table 3.7.: C Preprocessor directives related to conditional compilation
’static inline’ functions, or macros, which are used in the code. Let
the compiler optimize away the "no-op" case.”
The bottom line is that while the use of CPP is generally considered problematic, its legitimate uses have been identified and well understood. This
includes the use of conditional compilation, which is the predominant means
for variability management in systems software and the focus of the remainder
of this section.
3.3.2. Conditional Compilation
The semantic rules that arise from the CPP language specification are pretty
complex. Fortunately, for a quantitative analysis of (conditional) variability
implementations, only the rules that control conditional compilation are relevant. Still, identifying and quantifying the effects of conditional blocks in an
automated manner is not trivial.
Normally, the C preprocessor is called while compiling software through the
compiler driver. For the GNU Compiler Collection (GCC), this driver is the GCC
executable. The preprocessor assembles the input for the actual compiler run.
Its output is called the expanded compilation unit and is passed to the actual
compiler (the CC 1 executable).
The C preprocessor is controlled via special preprocessor directives that are
specified in the source code or given as command-line parameters. Table 3.7
shows the preprocessor directives that are useful for implementing variability.
The directives #if, #elif, #ifdef and #ifndef declare conditional blocks,
which means blocks that are being skipped or copied to the output stream
52
3.3. Variability with the C Preprocessor
depending on the result of the evaluation of their argument. This argument is
a logical expression that consists either of a single CPP flag (such as the IDENT
parameter of the #ifdef and #ifndef directives) or a logical expression (such
as the EXPR parameter of the #if and #elif directives) that contains multiple
flags. Additionally, conditional blocks can be nested. Nested blocks are only
relevant if the block in which they are nested is included in the expanded
compilation unit; Otherwise, their content is unconditionally ignored.
In the context of conditional compilation, the C preprocessor inserts slices
of source code into its output stream according to the definition of the given
CPP flags. The result of this process is then passed to the compiler for further
processing. These slices of token streams are the fine-grained variability points
in implementation source files (cf. Section 2.1.3). Therefore, a CPP block is
always a range of lines in a source file.
Note that the concept of conditionally built blocks can be easily extended
to cover both #ifdef blocks and conditionally built source files by applying
a conceptual normalization. In theory, the variability as expressed in the
makefile could also be implemented by guarding the complete source file with
an #ifdef block that is enabled under the same conditions as expressed in the
makefile. This allows handling every conditionally compiled source file as a
configuration-controlled block. Therefore, and for the sake of simplicity, the
term variability points in the implementation space covers both configuration
conditionally compiled #ifdef blocks as well as the conditionally compiled
source files.
3.3.3. Expressing the Variability of CPP in Propositional Formulas
On a very abstract level, the result of the variability extraction process is a
formal model that expresses how the selected configuration affects the presence (i.e., the selection or deselection) of variability points. The input to CPP
consists of a configuration (i.e., a set of CPP identifiers specified in the CPP expressions), which controls the variability points (i.e., CPP blocks in the source
code). The variability extraction process starts with the identification of the
CPP flags in the #ifdef expressions in the source code and the #ifdef blocks
that they control. Both, CPP blocks as well as CPP flags, are represented by
atoms, that is, propositional variables, in the resulting propositional formulas.
The mechanics of conditional compilation by the CPP is analyzed as a black
box in order to systematically deduce a set of rules that describe the behavior
accurately. The principle is depicted in Figure 3.6: The variation of CPP
identifiers on the left side affects the selection of #ifdef blocks on the right
side.
In the following, I describe these mechanics of the CPP as a set of rules
that stem from the language constructs provided for conditional compilation
53
C preprocessor
#ifdef Blocks
CPP Identifiers
Chapter 3. Extraction and Validation of Variability Implementations
Figure 3.6.: Abstract view on the variability that is implemented by C preprocessor
(thus, black-box–like approach). The described rules allow constructing the
variability model as expressed by the CPP. After all presence conditions (PCs)
have been calculated, ϕCPP is simply the conjunction of all PCs:
^
ϕCPP :=
PC (bi ) | i : non-nested, not in -#elif cascade
i
The result is the implementation variability model, which can be combined
with ϕKCONFIG as described in Section 3.1 and ϕK BUILD , as described in Section 3.2
by conjunction.
Rule 1: Translation of Expressions in #ifdef blocks
Consider the following #ifdef block:
#ifdef CONFIG_SMP && CONFIG_ARM
// code, b1
#endif
For the #ifdef block b1 , its presence condition PC (b1 ) translates directly from
its expression, which reads CONFIG_SMP && CONFIG_ARM. For simplicity, the
helper function expression(expr) indicates if the selection of #ifdef flags
leads to the selection of the #ifdef block.
PC (b1 ) ⇐⇒ expression(CONFIG_SMP ∧ CONFIG_ARM)
The helper function expression(expr) assigns each CPP flags an atom of the
propositional formula. In case of function-like CPP macros, this helper function
has the task to translate the implemented variability into a propositional
formula.
This step also does some normalizations that have become necessary in
particular for Linux v3.0 and later versions, which introduce a new idiom
in form of the three macros IS_ENABLED(option), IS_BUILTIN(option) and
IS_MODULE(option). These options do not increase the variability expressiveness that is implemented with the CPP, but allow the programmer to test the
state of a feature selection in KCONFIG in a convenient way. For the sake of
simplicity, the expression(expr) function normalizes such helper CPP macros
54
3.3. Variability with the C Preprocessor
appropriately. These normalizations are critical for the correct translation of
the variability that the programmers expresses with the #ifdef statements.
Without them, the extractor produces a formula that does not behave exactly
as the variability expressed by CPP.
Rule 2: Alternatives with #else
#ifdef CONFIG_SMP && CONFIG_ARM
// first implementation, b1
#else
// alternative implementation, b2
#endif
In this case, the presence condition of the block b2 is the negation of its
alternative:
PC (b2 ) ⇐⇒ ¬PC (b1 )
Rule 3: #elif cascades
The presence condition for an #ifdef block in an #elif cascade depends on
the presence of the preceding #ifdef block. This means that if a block is
selected, the CPP will not select any following block in the same #if cascade
regardless of the expression. To simplify the resulting propositional formulas,
this condition is expressed by the helper expression noPredecessor(bi ), which
evaluates to true if and only if there is no PC (bj ) with j < i that evaluates to
true:
noPredecessor(bi ) := true ⇐⇒ @j : PC (bj ) = true | j < i, bi , bj in same cascade
With this helper expression, the presence conditions for the #ifdef blocks as
given below becomes easy to specify:
#ifdef CONFIG_ARM
// arm specific code, b1
#elif CONFIG_PPC
// powerpc specific code, b2
#elif CONFIG_X86
// x86 specific code, b3
#else
// fallback code, b4
#endif
The first block b1 remains PC (b1 ) = expression(CONFIG_ARM). However,
the presence condition for b2 now reads:
55
Chapter 3. Extraction and Validation of Variability Implementations
PC (b2 ) = expression(b2 ) ∧ noPredecessor(b2 )
In this example, the block b4 is a simple #ifdef block without expression.
expression(b4 ) is therefore a tautology, which means that in this case the presence condition of this block depends on the helper function noPredecessor(b2 )
only.
Rule 4: Nesting
For nested #ifdef blocks, the presence of the inner block bi depends on the
selection of its outer block bo . For simplicity, the helper function parent(bi ) is
introduced:
parent(bi ) := true ⇐⇒ PC (bi ) ∧ PC (bo ) | bo the outer block
With this rule, the presence condition for the following code example is
expressed easily:
#ifdef CONFIG_X86
// code block bo
#ifdef CONFIG_SMP
// code block bi
#endif
// rest of block bo
#endif
In this case, the presence condition for bi results in:
PC (bi ) = expression(bi ) ∧ parent(bi )
Rule 5: Combination
With suitably chosen corner-cases for the helper functions, the Rules 1 to 4 can
be combined to express the variability of (almost) arbitrary #ifdef code: For
non-nested #ifdef blocks, parent(b) is a tautology. Similar for #ifdef blocks
that start an #elif cascade; here noPredecessor(b) is a tautology for the first
block in the cascade. The combined formula for the presence condition of a
block b then reads:
PC (b) = expression(b) ∧ parent(b) ∧ noPredecessor(b)
Rule 6: Handling of #define and #undef statements
The challenge with the #define and #undef statement is that they always act
only for blocks that appear lexically afterwards. This implies an ordering over
56
3.3. Variability with the C Preprocessor
all blocks, which cannot be represented in propositional formulas directly
since all atoms in a propositional variable are stateless.
The solution to this problem is loosely inspired by the SSA form [App98]
used in modern compilers such as the LLVM framework [LA04]. The first step
is to identify regions, in which the value of the conditional variable is invariant.
This means that every redefinition statement introduces a new region that
starts at the point of the redefinition statement and renames all dependent
propositional variables. For the following example, the identified regions are
depicted in Figure 3.7:
1
2
3
4
5
6
7
8
9
10
#if X // Block 0
#define A
#endif
#if Y // Block 1
#undef A
#endif
#if A // Block 2
#endif
Each time a redefinition statement introduces a new region, each variable
(e.g. in this case, A) is rewritten to a new variable (e.g. in this case, A’). The
remainder of this section explains how to express the logical relationships
between the original conditional variable and its rewritten version (e.g., A
and A’), which are added as additional conjunction to the existing presence
conditions.
For the given example, the challenge for establishing the resulting propositional formula is that the #define statement in Line 2 and the #undef statement
in Line 6 are effective only when the CPP selects Block b0 and Block b1 . The
idea is to introduce proxy variables (i.e., A → A0 ) and describe the logical
relationship between the two. This approach results in the following formula:
1
2
3
4
5
6
7
(
(
(
(
(
(
(
b0 ⇐⇒ X ) ∧
b1 ⇐⇒ Y ) ∧
b2 ⇐⇒ A00 ) ∧
b0 → A0 ) ∧
¬b0 → ( A ⇐⇒ A0 )) ∧
b1 → ¬A00 ) ∧
¬b1 → ( A0 ⇐⇒ A00 ))
Line 1 to Line 3 represent the “basic” presence conditions, which are derived
directly from the #ifdef expressions for this simple example. In Line 3 the
conditional variable is already rewritten to A00 . Lines 4 to 7 contain implications
that control the relationships between the original and rewritten versions of
the conditional variable. For both Block 0 and Block 1, two implications (Lines
4 and 5 for Block 0, and Lines 6 and 7 for Block 1) describe the situation
with respect to the selection of the block: If it is selected, then the rewritten
57
Chapter 3. Extraction and Validation of Variability Implementations
start of file
#if CONFIG_X
"CONFIG_A" → A
#define CONFIG_A
"CONFIG_A" → A0
#if CONFIG_Y
#undef CONFIG_A
"CONFIG_A" → A00
#if CONFIG_A
// source code
end of file
Figure 3.7.: A source file with three #ifdef blocks. The upper part of each block contains the #ifdef expression, the lower part #define and #undef statements in the block. The basic idea for encoding a flow of #define and
#undef statements into a propositional formula is to introduce “scopes”.
All references to a variable (A) that occur after a #define or #undef
statement are rewritten.
version of the conditional variable is set to true – if not, then both versions are
equivalent.
Generally speaking, this “rewriting” rule constitutes an extension to the
presence condition as stated in Rule 1: Here, the helper function expression(bi ) is extended to substitute all propositional variables with the rewritten
version that is active in the “region” of the corresponding conditional block.
The resulting formula then correctly represents the implementation variability
model of CPP programs that may include the statements #define and #undef.
3.3.4. Non-Boolean expression
The algorithm and rules presented in this subsection cover the subset of the
CPP statements that contain conditional compilation with Boolean configuration flags only. Fortunately, as far as conditional compilation is concerned, the
decision whether a block is selected or not is intrinsically Boolean. Therefore,
each expression that involves non-Boolean flags and operators (e.g., ==, <, and
similar) is replaced by a new free logic variable. For example, the expression
#if CONFIG_BUFFER > 1024 is rewritten to #if defined CONFIG_COMPARATOR_1.
In future work, this limitation could be avoided by using a satisfiability modulo
theories (SMT) [Wik13b] or a constraint solving problem (CSP) [Wik13a]
engine, instead of a SAT solver.
58
3.3. Variability with the C Preprocessor
1
2
3
4
5
6
7
8
config FEATURE_CHECK_UNICODE_IN_ENV
bool "Check LANG environment variable "
default n
depends on UNICODE_SUPPORT && !UNICODE_USING_LOCALE
help
With this option on, Unicode support is activated only when
the LANG variable has the value of the form "xxxx.utf8".
Otherwise, Unicode support will be always enabled and active.
(a) The configuration item FEATURE_CHECK_UNICODE_IN_ENV controls how the
environment can influence the unicode feature in B USY B OX.
1
2
3
4
#undef CONFIG_FEATURE_CHECK_UNICODE_IN_ENV
#define ENABLE_FEATURE_CHECK_UNICODE_IN_ENV 0
#define IF_FEATURE_CHECK_UNICODE_IN_ENV(...)
#define IF_NOT_FEATURE_CHECK_UNICODE_IN_ENV(...) __VA_ARGS__
(b) The build-system constructs a set of CPP statements when the item
FEATURE_CHECK_UNICODE_IN_ENV is not enabled.
Figure 3.8.: The build system of B USY B OX translates each KCONFIG item to four CPP
macros.
3.3.5. Extraction of Code Variability in B USY B OX
The extraction of ϕCPP is conceptually straightforward to apply on other systems. For most systems, my ϕCPP extractor can be directly applied. Unfortunately, there are some systems that impose additional difficulties. In this
subsection, I apply the extractor on B USY B OX as an exemplary more complicated test subject. B USY B OX has already been introduced in Section 3.2.7.2 on
page 47.
In B USY B OX, the technical challenge lies in the details how the developers
have implemented variability in the source code. Similar to Linux, B USY B OX
uses (only slightly modified versions of) KCONFIG and K BUILD. Unlike Linux,
however, the build system constructs for each KCONFIG symbol four additional
helper macros for development convenience. Figure 3.8 illustrates this process
for the item FEATURE_CHECK_UNICODE_IN_ENV.
The redundancy of these macros allow programmers to use them freely at
their convenience, as the B USY B OX construction process ensures that they are
all set consistent to the chosen user configuration. Therefore, the extraction
of ϕCPP requires an additional, B USY B OX-specific normalization step. Instead
of integrating additional B USY B OX-specific implementation details into the
existing extractor, the normalization is implemented as a source-to-source
transformation tool named B USYFIX.
The first CPP statement in Figure 3.8b follows the convention in Linux and
59
Chapter 3. Extraction and Validation of Variability Implementations
is the only variant that the extractor for ϕCPP directly understands. B USYFIX
identifies the forms in lines 2 to 4 in the implementation, and normalizes
them to the first from. Without this normalization, the extractor identifies
4,019 #ifdef blocks in B USY B OX version 1.20.2. After normalizing the source
tree, the extractor operates without further modifications, and identifies 1,291
additional (+24.3%) #ifdef blocks. This increase of identified variation points
has significant effects on the case-studies that I present in Chapter 4.
3.3.6. Experimental Validation of the Extracted Formula
I validate the correct behavior of the rules constructed in the previous subsection experimentally, because the resulting propositional formulas of real-world
source files, such as whole driver implementations in Linux, are too complex
to validate manually. The approach follows Figure 3.6 by identifying the CPP
identifiers in the #ifdef expressions (left side) and systematically applying
each permutation of #ifdef flags (right side).
For the validation, I have systematically constructed a set of synthetic files
with CPP statements that test all rules described in Section 3.3.3. For each file,
I collect all CPP flags and permute all possible selections. For each selection of
flags, I call the CPP tool and record the thereby selected blocks. The result is
the empirically determined set of configuration flags and corresponding “valid”
block selections according to the actual semantics of CPP.
While the run-time complexity of the empirical validation is exponential
with the number of conditional blocks and CPP flags, it is still a valuable asset
for developing and verifying that the generated formulas represent the CPP
semantics correctly.
3.3.7. Further Related Work
The analysis of large scale software projects with transformation systems
has been proposed before by several authors; DMS [Bax02] proposed by
Baxter is probably the most renowned one. In context of this framework, an
approach has been published [BM01] that aims at simplifying CPP statements
by detecting dead code and simplifying CPP expressions. While DMS uses
concrete configurations to evaluate the expressions partially [JGS93], my
approach does not require concrete values for CPP identifiers, but produces a
logical model in form of a propositional formula. This formula can either be
evaluated directly or can be combined with further constraints like the ones
extracted from the feature model. The concepts in this thesis would integrate
very well in the DMS framework.
Hu et al. [Hu+00] propose to analyze conditional compilation with symbolic
execution. Their approach map conditional compilation to execution steps:
60
3.3. Variability with the C Preprocessor
Inclusion of headers map to calls, alternative blocks to branches in a control
flow graph (CFG), which is then processed with traditional symbolic execution
techniques. Latendresse [Lat04] improves this technique by using rewrite
systems in order to find presence conditions for every line of code. Similarly to
my approach, the presence conditions of all conditional blocks are (implicitly)
calculated during the process as well. While it would be possible to use the
presence conditions learned with symbolic execution, this would require some
considerable engineering effort. In contrast to that, my approach does scale
(the algorithm for generating the Boolean formula grows linearly with respect
to the number of blocks) to code bases of millions of lines of code. This is a
prerequisite for the application on large-scale system software, such as the
Linux kernel.
Other approaches extend the C/C++ parser by CPP directives. Badros and
Notkin [BN00] propose the PCp3 framework to integrate hooks into the parser
in order to perform many common software-engineering analyses. Garrido
[Gar05] extends the parser with the concept of conditional ASTs, which
enables preprocessor-aware refactorings on CPP-based source files. The most
sophisticated approaches so far, however, have been presented with S UPER C
by Gazzillo and Grimm [GG12] and T YPE C HEF by Kästner et al. [Käs+11].
Both works, which have already been discussed in Section 2.2.3, result in
token trees that inhibit the presence conditions for all blocks on the edges of
the graph. The approach that I present in this section, however, is much more
efficient and can be combined with other variability constraints easily.
The approach for extracting the variability of the CPP that I present in
this section consists in essence of a transformation system that simulates the
mechanics of the CPP tool. The result is the formula ϕCPP , which expresses the
variability of the conditional compilation of a source file. The formula ϕCPP
can then be checked with for instance a SAT solver or combined with other
constraints.
3.3.8. Summary: Variability with the C Preprocessor
Linux, and many other system software as well, implements a considerable
amount of features with the means of the C preprocessor. The CPP is a widely
employed and well-understood asset for every experienced C programmer and
has both merits and risks. The general conclusion with respect to this topic is
that the CPP is best used where it is strong, and this strength is conditional
compilation.
Unfortunately, the means for conditional compilation provided by the CPP
is limited to simple imperative statements. Conceptually, this cannot compete
with newer language extensions that provide better abstractions for handling
alternatives and optional feature implementations. This makes the CPP an
61
Chapter 3. Extraction and Validation of Variability Implementations
assembly-level means for variability management.
In this section, I have presented an approach for translating the variability
expressed by conditional compilation with the CPP into the propositional
formula ϕCPP by using a set of semi-formal rules. The resulting implementation
is tested systematically against both synthetically constructed test-cases, as
well as real-world examples. The tool is validated by comparing the results
of the tool with the actual CPP implementation from the GCC tool suite. The
result, the formula ϕCPP , represents the missing piece that completes the
holistic formula ϕ = ϕKCONFIG ∧ ϕK BUILD ∧ ϕCPP . The next chapter uses this ϕ
extensively to answer the questions stated in Section 1.2.
62
3.4. Chapter Summary
3.4. Chapter Summary
The extraction and validation of variability from system software is a complex
task with many engineering challenges. This is true in particular for operating
systems such as Linux, which has evolved over decades. Linux (mostly) uses
traditional tools such as CPP and MAKE for implementing alternative and
optional features. In addition to that, features get declared in a DSL called
KCONFIG, which allows declaring feature interdependencies, alternatives and
similar constraints.
In order to reveal inconsistencies and bugs, the declared and implemented
variability needs to be made accessible for development and analysis tools.
For Linux and similar system software that implement variability in a nonhomogeneous manner and in different source artifacts, my approach makes
use of variability extractors that handle all sources of variability. This poses
both engineering, as well as conceptual challenges, which I address in this
chapter.
The results are the propositional formulas ϕKCONFIG , ϕK BUILD , and ϕCPP , which
are constructed in a way that allows the construction of ϕ: the holistic view of
the variability in a software project. The next chapter uses this formalization
extensively in three different use cases.
63
4
Case Studies
I wish defconfig was actually something
useful like this, instead of.. what the hell is it
exactly? No-one even seems to agree, other
than "random selection of options, many of
which were removed n years ago"
(Dave Jones, July 2012,
SuSE Kernel Mailing-list)
Technically, the intensional and extensional sides of variability are managed by different concepts, tools, and languages. Many configurability-related
defects are caused by inconsistencies that result from the fact that configurability is defined by two (technically separated, but conceptually related)
models: the configuration space ϕConfiguration , and the implementation space
ϕImplementation . With the formalization and extractors presented in Chapter 3,
these two formulas are now available as:
ϕConfiguration := ϕKCONFIG
ϕImplementation := ϕK BUILD ∧ ϕCPP
With this formalization of variability, I present three practical applications
in this section. First, I show what kind of configuration inconsistencies result
from the separate development of the configuration and implementation space,
and how tool support can avoid them during development. Second, I show
an approach that allows extending the coverage of tools for static analysis
that require preprocessed source code for type checking. Third, I present an
automated approach to tailor a Linux configuration for a given use-case.
65
Chapter 4. Case Studies
Related Publications
The ideas and results of this chapter have partly also been published as:
[Tar+11b]
Reinhard Tartler, Daniel Lohmann, Julio Sincero, Wolfgang
Schröder-Preikschat. “Feature Consistency in Compile-TimeConfigurable System Software: Facing the Linux 10,000 Feature
Problem”. In: Proceedings of the ACM SIGOPS/EuroSys European
Conference on Computer Systems 2011 (EuroSys ’11). (Salzburg,
Austria). Edited by Christoph M. Kirsch and Gernot Heiser.
New York, NY, USA: ACM Press, Apr. 2011, pages 47–60. DOI:
10.1145/1966445.1966451
[Tar+11a]
Reinhard Tartler, Daniel Lohmann, Christian Dietrich,
Christoph Egger, Julio Sincero. “Configuration Coverage in
the Analysis of Large-Scale System Software”. In: Proceedings
of the 6th Workshop on Programming Languages and Operating
Systems (PLOS ’11). (Cascais, Portugal). Edited by Eric Eide,
Gilles Muller, Olaf Spinczyk, Wolfgang Schröder-Preikschat.
New York, NY, USA: ACM Press, 2011, 2:1–2:5. DOI: 10.1145/
2039239.2039242
[Tar+12]
Reinhard Tartler, Anil Kurmus, Bernard Heinloth, Valentin
Rothberg, Andreas Ruprecht, Daniela Doreanu, Rüdiger
Kapitza, Wolfgang Schröder-Preikschat, Daniel Lohmann. “Automatic OS Kernel TCB Reduction by Leveraging Compile-Time
Configurability”. In: Proceedings of the 8th International Workshop on Hot Topics in System Dependability (HotDep ’12). (Los
Angeles, CA, USA). Berkeley, CA, USA: USENIX Association,
2012, pages 1–6
[Kur+13]
Anil Kurmus, Reinhard Tartler, Daniela Dorneanu, Bernhard
Heinloth, Valentin Rothberg, Andreas Ruprecht, Wolfgang
Schröder-Preikschat, Daniel Lohmann, Rüdiger Kapitza. “Attack Surface Metrics and Automated Compile-Time OS Kernel
Tailoring”. In: Proceedings of the 20th Network and Distributed
Systems Security Symposium. (San Diego, CA, USA, Feb. 24–27,
2013). The Internet Society, 2013. URL: http://www4.cs.
fau.de/Publications/2013/kurmus_13_ndss.pdf
66
4.1. Configuration Consistency
4.1. Configuration Consistency
Configurability as a system property includes two separate – but related –
aspects: implementation and configuration. Kernel developers implement
configurability in the source code. Users configure the operating system to
derive a concrete variant that fits their purposes. Based on the internal model
of features and constraints, the configuration tool guides the user through the
configuration process by a topic-oriented view on the available features. In
today’s operating systems, this extra guidance is crucial because of the sheer
enormity of available features: The Linux kernel is configured with KCONFIG
and provides more than 12,000 configuration options. This is a lot of variability
– and the source of many bugs that could easily be avoided by better tool
support.
In this thesis, I mitigate this problem by providing tool support and approaches that are useful for system software engineers.
The general idea for finding configurability bugs is to formalize both, the
configuration space and the implementation space, in Linux into propositional
formulas, and use a SAT solver to find tautologies and contradictions. In
this section, I explain how to use these formalizations to reveal configuration
inconsistencies.
4.1.1. Manifestations of Configuration Inconsistencies
In essence, there are two types of integrity issues. Symbolic integrity issues
arise from referential errors of single identifiers in the logical constraints
between the #ifdef blocks in the code and the dependencies declared in
KCONFIG. Such problems are in general rather easy to follow and fix. Logic
integrity issues, however, involve more than a single identifier and the logical
implications are often harder to follow. In the following, I discuss both kinds
of inconsistencies with real-world examples from Linux.
Listing 4.1 corrects a simple feature misnaming. The proposed change
is presented in the format that is preferred by Linux kernel developers and
explained in detail in Appendix B.11 . The following patch corrects a critical
issue in the source file kernel/smp.c:
1
2
3
4
--- a/kernel/smp.c
+++ b/kernel/smp.c
-#ifdef CONFIG_CPU_HOTPLUG
+#ifdef CONFIG_HOTPLUG_CPU
Listing 4.1: Patch for a symbolic defect
1
In a nutshell: This format is called the “unified diff format” and is a standard format for interchanging
code changes that is understood by the UNIX patch(1) tool. The format describes changes with a
granularity of source lines: The lines starting with -/+ are being removed/added.
67
Chapter 4. Case Studies
This issue, which was present in Linux 2.6.30 and has been included into the
official Linux kernel shortly thereafter, is an example of a symbolic integrity
violation; the program text references a feature that does not exist in KCONFIG.
As a consequence, the actual implementation of the HOTPLUG_CPU feature is
incomplete, and in fact, faulty. To understand the impact of this defect,
consider the context of the patch, which is part of the Linux multi-processor
implementation:
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
static int
hotplug_cfd(struct notifier_block *nfb, unsigned long action, void *hcpu)
{
long cpu = (long)hcpu;
struct call_function_data *cfd = &per_cpu(cfd_data, cpu);
switch (action) {
case CPU_UP_PREPARE:
case CPU_UP_PREPARE_FROZEN:
if (!zalloc_cpumask_var_node(&cfd->cpumask, GFP_KERNEL,
cpu_to_node(cpu)))
return NOTIFY_BAD;
break;
#ifdef CONFIG_CPU_HOTPLUG
case CPU_UP_CANCELED:
case CPU_UP_CANCELED_FROZEN:
case CPU_DEAD:
case CPU_DEAD_FROZEN:
free_cpumask_var(cfd->cpumask);
break;
#endif
};
return NOTIFY_OK;
}
Listing 4.2: Context of the patch from Listing 4.1
The issue here is that the misspelling in Line 15 prevents the following four
case statements to be ever compiled. The consequence is that certain resources
are not properly freed, which causes a resource leak and prevents a stable
operation of the CPU–hot-plugging feature. This bug remained undetected in
the kernel code base for more than six months, but could have been trivially
revealed if appropriate tools had been available and used.
Such symbolic integrity violations indicate a configuration versus implementation space mismatch with respect to a feature identifier. However, consistency
issues also occur at the level of feature constraints. To show the difference,
consider the following change, which fixes a logic integrity violation:
68
4.1. Configuration Consistency
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
--- a/arch/x86/include/asm/mmzone_32.h
+++ b/arch/x86/include/asm/mmzone_32.h
@@ -61,11 +61,7 @@ extern s8 physnode_map[];
static inline int pfn_to_nid(unsigned long pfn)
{
-#ifdef CONFIG_NUMA
return((int) physnode_map[(pfn)
/ PAGES_PER_ELEMENT]);
-#else
- return 0;
-#endif
}
/*
Listing 4.3: Patch for a logic defect
The patch itself removes four lines and does not look too complicated – the particularities of the issue it fixes stem from the context, which is not obvious even
to experienced Linux developers. In the source, the affected pfn_to_nid()
function is nested within a larger #ifdef block that is only selected when
the CPP identifier CONFIG_DISCONTIGMEM is active. According to the KCONFIG
model, however, the DISCONTIGMEM feature depends on the NUMA feature,
which means that it also implies the selection of NUMA in any valid configuration. As a consequence, the #ifdef CONFIG_NUMA CPP directive is superfluous
and the #else branch is dead. Therefore, the situation is unnecessarily complicated and can be significantly simplified by removing all three preprocessor
directives – which is what the patch implements.
Compared to symbolic integrity violations, logic integrity violations are generally much more difficult to analyze and fix; the experience from submitting
patches to Linux developers shows that most symbolic violations are caused
by misspellings or oversights.
Note that the patch in Listing 4.3 does not fix a real bug – it only improves
the readability of the Linux source-code by removing some dead code and
superfluous #ifdef statements. Some readers might consider this as “less
relevant cosmetic improvement”; however, such “cruft” (especially if it contributes to “#ifdef hell”, cf. Section 2.2.4) causes long-term maintenance costs
and impedes the general accessibility of the source. This problem has been
observed in the literature before, for instance by Le, Walkingshaw, and Erwig
[LWE11], or by Lohmann et al. [Loh+06].
These two kinds of consistency issues are not uncommon in Linux. In fact, my
prototype implementation reveals 3,426 configuration defects in Linux version
v3.2 in total, many of which indicate serious issues in the source-code of Linux.
This amount of inconsistencies calls for tool support so that the responsible
software maintainer is able to address these problems systematically.
69
Chapter 4. Case Studies
4.1.2. Ensuring Configuration Consistency
The general idea for ensuring configuration consistency, and revealing defects that violate it, is to extract all configurability-related information from
all variability sources into a common representation, the holistic variability
model ϕ. This model allows detecting configuration defects, which are defined
as follows:
Definition 1 A (configurability) defect is a configuration-conditional variability point that is either dead (never included), or undead (always included),
under the precondition that its parent is included.
Examples for variability points in Linux are: KCONFIG options, conditionally
built source files, and #ifdef blocks. The CONFIG_NUMA example discussed in
Listing 4.3 bears two defects in this respect: Block2 (line 7 to 10) is undead and
Block3 (line 11) is dead.
Essential for the analysis of configurability problems is a common representation of the variability across different software artifacts. The fact that
feature implementations are spread over KCONFIG, MAKE rules and CPP blocks
requires a normalization step, so that the variability across these inhomogeneous feature implementations can be compared and systematically analyzed.
The variability in both, the configuration and implementation space, can be
directly translated to propositional logic. By this, the detection of configuration
problems boils down to a satisfiability problem.
The general idea of my approach is to individually convert each variability
source (i.e., makefiles, source code, and KCONFIG files) to a common representation in form of a sub-model, and then combine these sub-models into a
holistic model that contains the whole variability of the software project. This
makes it possible to analyze each sub-model, as well as their combination, in
order to reveal inconsistencies across sub-models.
Linux (and many other systems) keep the configuration space (ϕConfiguration )
and their implementation space (ϕImplementation ) separated. The complete model
ϕ is constructed by calculating the conjunction of both formulas:
ϕ := ϕConfiguration ∧ ϕImplementation
(4.1)
ϕ 7→ {0, 1} is a Boolean formula over all variation points of the system;
ϕConfiguration and ϕImplementation are the Boolean formulas representing the constraints of the configuration and implementation spaces, respectively. A sufficiently precise translation of the variability of different artifacts into the
formulas ϕConfiguration and ϕImplementation is crucial for the correct identification
of defects.
With this model, I validate the implementation for configurability defects,
that is, if the conditions for the presence of the block (BlockN ) are fulfillable
70
4.1. Configuration Consistency
Configuration Space
Implementation Space
MEMORY MODEL
FLATMEM
SPARSEMEM
DISCONTIGMEM
#ifdef CONFIG DISCONTIGMEM
// Block1
static . . . int pfn_to_mid(. . .)
# ifdef CONFIG NUMA
// Block2
# else
// Block3
# endif
#endif
NUMA
n
depends o
ϕConf. = (FLATMEM → MEMORY MODEL)
∧ (DISCONTIGMEM → MEMORY MODEL)
∧ (SPARSEMEM → MEMORY MODEL)
∧ (NUMA → MEMORY MODEL)
∧ (DISCONTIGMEM → NUMA)
Configuration Space Constraints
ϕImpl. = (Block1 ↔ DISCONTIGMEM)
sat(ϕcombined ∧ BlockN )
dead?
undead? sat(ϕcombined ∧ ¬BlockN
∧ parent(BlockN ))
Configurability Defects
∧ (Block2 ↔ Block1 ∧ (NUMA)
∧ (Block3 ↔ Block1 ∧ ¬Block2 )
Implementation Space Constraints
Figure 4.1.: General approach for finding configuration inconsistencies
in the model ϕ. For example, consider Figure 4.1: The formula shown for dead
blocks is satisfiable for Block1 and Block2 , but not for Block3 . Therefore, Block3
is considered to be dead; similarly the formula for undead blocks indicates
that Block2 is undead.
4.1.2.1. Practical Challenges
In practice, the implementation of this approach for real-world large-scale
system software faces the following challenges:
Performance. When dealing with huge code bases, the development tools
must not require an unreasonable amount of run-time. More importantly,
I also aim at supporting programmers at development time when only
a few files are of interest. Therefore, supporting incremental builds
efficiently is an essential property.
Flexibility. Projects that handle thousands of features will eventually contain
desired inconsistencies with respect to their variability. Gradual addition
or removal of features and large revisions of source code are examples
of efforts that may lead to such inconsistent states within the lifetime
of a project. Also, evolving projects may change their requirements
regarding their variability descriptions. Therefore, a tool that checks
for configuration problems should be flexible enough to incorporate
information about desired issues in order to deliver precise and useful
results.
71
Chapter 4. Case Studies
Figure 4.2.: Principle of operation of the UNDERTAKER tool
In order to achieve both, performance and flexibility, the implementation
of the approach needs to take the particularities of the analyzed software
project into account. Moreover, the precision of the configurability extraction
mechanism impacts the rate of false positive and false negative reports. As
Linux has developed the KCONFIG configuration tool and language to describe
configuration variability, the configurability extraction needs to be tightly
tailored.
4.1.2.2. Outline of the Implementation of the UNDERTAKER Tool
I have implemented the approach presented in this section in form of a prototype tool called UNDERTAKER. The task of the UNDERTAKER tool is to identify
(and eventually bury) dead and undead CPP-Blocks. The basic principle of operation is depicted in Figure 4.2: The different sources of variability are parsed
and transformed into propositional formulas. UNDERTAKER makes use of the
extractors presented Chapter 3. The outcome is fed into the crosschecking
engine, and solved using the PICOSAT [Bie08] package.
The tool scans each .c and .h file in the source tree individually. This
allows developers to focus on the part of the source code they are currently
72
4.1. Configuration Consistency
1
#B6:arch/parisc/include/asm/mmzone.h:40:1:logic:undead
2
B2 &
!B6 &
3
4
5
6
7
8
9
10
11
12
13
14
15
16
(B0
(B2
(B4
(B6
(B9
<->
<->
<->
<->
<->
!_PARISC_MMZONE_H) &
B0 & CONFIG_DISCONTIGMEM) &
B2 & !CONFIG_64BIT) &
B2 & !B4) &
B0 & !B2) &
(CONFIG_64BIT -> CONFIG_PA8X00) &
(CONFIG_ARCH_DISCONTIGMEM_ENABLE -> CONFIG_64BIT) &
(CONFIG_ARCH_SELECT_MEMORY_MODEL -> CONFIG_64BIT) &
(CONFIG_CHOICE_11 -> CONFIG_SELECT_MEMORY_MODEL) &
(CONFIG_DISCONTIGMEM -> !CONFIG_SELECT_MEMORY_MODEL &
CONFIG_ARCH_DISCONTIGMEM_ENABLE | CONFIG_DISCONTIGMEM_MANUAL) &
(CONFIG_DISCONTIGMEM_MANUAL -> CONFIG_CHOICE_11 &
CONFIG_ARCH_DISCONTIGMEM_ENABLE) &
(CONFIG_PA8X00 -> CONFIG_CHOICE_7) &
(CONFIG_SELECT_MEMORY_MODEL -> CONFIG_EXPERIMENTAL |
CONFIG_ARCH_SELECT_MEMORY_MODEL)
Figure 4.3.: Exemplary output of the UNDERTAKER tool. This propositional formula has
been automatically produced by the UNDERTAKER tool for the situation
presented in Listing 4.3.
working on and to get instant results for incremental changes. The results
come as defect reports per file.
Finding Configuration Defects in Linux
To illustrate how configuration defects manifest in Linux and how the UN DERTAKER tool detects them, I demonstrate a concrete case that was found
in Linux v2.6.35. For the configuration-implementation defect from Figure 4.1
UNDERTAKER produces a report that reads:
Found Kconfig related DEAD in arch/parisc/include/asm/mmzone.h,
line 40: Block B6 is unselectable, check the SAT formula.
Based on this information, the developer now revisits the KCONFIG files. The
basis for the report is a formula that is falsified by the SAT solver. For this
particular example, my UNDERTAKER development prototype automatically
produces the propositional formula depicted in Figure 4.3, based on the results
of the extractors presented in Chapter 3.
This formula can be deciphered easily by examining its parts individually.
The first line shows an “executive summary” of the defect; here, Block B6,
which starts in Line 40 in the file arch/parisc/include/asm/mmzone.h,
is a logic configuration defect in form of a block that cannot be unselected
(“undead”). Lines 4 to 8 in the listing show the presence conditions (PCs) of
the corresponding blocks (cf. Section 3.3); they all start with a block variable
73
Chapter 4. Case Studies
and by construction cannot cause the formula to be unsatisfiable. From the
structure of the formula, the developer sees that Block B0 implements the CPP
“include guard” and therefore encloses all other blocks. Moreover, the way the
Blocks B4 on Line 6 and B6 on Line 7 reference B2 indicate that B2 encloses
B4 and B6. Lines 9 et seqq. contain the extracted implications from KCONFIG
(cf. Section 3.1). In this case, it turns out that the KCONFIG implications from
Line 9 to 16 show a transitive dependency from the KCONFIG item CONFIG_DISCONTIGMEM (cf. Block B2, Line 5) to the item CONFIG_64BIT (cf. Block
B4, Line 6). This means that the KCONFIG selection has no impact on the
evaluation of the #ifdef expression and the code can thus be simplified. The
resulting patch has been proposed to the Linux developers in May 2010:2
1
2
3
4
diff --git a/arch/parisc/include/asm/mmzone.h b/arch/parisc/include/asm/mmzone.h
--- a/arch/parisc/include/asm/mmzone.h
+++ b/arch/parisc/include/asm/mmzone.h
@@ -35,6 +35,1 @@ extern struct node_map_data node_data[];
5
6
7
8
9
10
11
-#ifndef CONFIG_64BIT
#define pfn_is_io(pfn) ((pfn & (0xf0000000UL >> PAGE_SHIFT)) == (0xf0000000UL >>
PAGE_SHIFT))
-#else
-/* io can be 0xf0f0f0f0f0xxxxxx or 0xfffffffff0000000 */
-#define pfn_is_io(pfn) ((pfn & (0xf000000000000000UL >> PAGE_SHIFT)) == (0
xf000000000000000UL >> PAGE_SHIFT))
-#endif
This is one of the more complicated examples. Most of the defects reports
are in fact only a few lines long and are therefore much easier to comprehend.
4.1.3. Application on Linux
Table 4.1 (upper half) summarizes the defects that UNDERTAKER 1.1 finds in
Linux 2.6.35, differentiated by subsystem. When counting defects in Linux,
some extra care has to be taken with respect to architectures: Linux employs a
separate KCONFIG-model per architecture that may also declare architecturespecific features. Hence, it is necessary to run the defect analysis over every
architecture and intersect the results to avoid counting, for example, MIPSspecific blocks of the code as dead when compiling for x86. The code in the
arch/ subdirectory is architecture-specific by definition and only checked
against the configuration model of the respective architecture.
4.1.3.1. Distribution of Configurability-Defects
Most of the 1,776 defects are found in arch/ and drivers/ subdirectories of
Linux, which together account for more than 75 percent of the configurabilityrelated #ifdef-blocks. For these subsystems, there are more than three defects
2
http://lkml.org/lkml/2010/5/12/202
74
4.1. Configuration Consistency
subsystem
#ifdefs
arch/
drivers/
fs/
include/
kernel/
mm/
net/
sound/
virt/
other subsystems
33757
32695
3000
7241
1412
555
2731
3246
53
601
345
88
4
6
7
0
1
5
0
4
581
648
13
11
2
1
49
10
0
1
926
736
17
17
9
1
50
15
0
5
P
85291
460
1316
1776
150 (1)
38 (1)
88 (0)
24 (0)
214 (22)
116 (20)
21 (2)
77 (0)
364 (23)
154 (21)
109 (2)
101 (0)
fix proposed
confirmed defect
confirmed rule-violation
pending
logic
symbolic
total
Table 4.1.: Upper half: #ifdef blocks and defects per subsystem in Linux version
2.6.35; Lower half: acceptance state of defects (bugs) for which I have
submitted a patch. These results are also published in [Tar+11b].
per hundred #ifdef-blocks, whereas for all other subsystems this ratio is below
one percent (net/ below two percent). These numbers support the common
observation (e.g., [Eng+01]) that “most bugs can be found in driver code”,
which apparently also holds for configurability-related defects.
Even though the majority of defects (74%) are caused by symbolic integrity
issues, UNDERTAKER also reveals 460 logic integrity violations, which would be
a lot harder to detect by “developer brainpower”.
4.1.3.2. Performance
A full analysis of kernel 2.6.35 processes 27,166 source files with 82,116
configurability-conditional code blocks, and 761 KCONFIG files defining 11,283
features. This analysis produces the results as shown in Table 4.1 and takes
around 30 minutes on a modern Intel quad core with 2.83 GHz and 8 GB
RAM.
This run time of UNDERTAKER tool is already appropriate for regular, nightly
scans of the Linux code base3 . Additionally, the fact that UNDERTAKER operates
on single source files makes it much more suited for integration into incremental Linux builds, which are the much more common use case for Linux
developers. Figure 4.4 depicts the file-based run time for the Linux source
3
In fact, I have conducted such nightly runs on the very latest snapshot of Linux during the whole
development of UNDERTAKER over nearly two years, and did help to detect bugs and performance
regressions.
75
Chapter 4. Case Studies
< 0.5 s
< 5s
< 30 s
93.69%
99.65%
100%
Figure 4.4.: Processing time for 27,166 Linux source files
base: 94 percent of all source files are analyzed in less than half a second;
less than one percent of the source files take more than five seconds and only
four files take between 20 and 30 seconds. The upper bound (29.1 seconds) is
caused by kernel/sysctl.c, which handles a very high number of features;
changes to this file often require a complete rebuild of the kernel anyway. For
an incremental build that affects about a dozen files, the UNDERTAKER tool
typically finishes in less than six seconds.
4.1.3.3. Qualitative Analysis of the Results
To evaluate the quality of the findings, I have proposed changes to the official
kernel maintainers. This involves analyzing the defect reports, proposing
a source code change that suffices the submission guidelines, and actually
submitting the proposed patch to the responsible kernel maintainers via email.
The first patches were submitted in February 2010 based on UNDERTAKER
version 1.1, at which time Linux version 2.6.33 has just been released. Most
of the patches entered the mainline kernel tree during the merge window of
version 2.6.36. Figure 4.5 depicts the whole workflow.
Every defect first gets analyzed. This requires locating the position of the
defect in the source-code and to understand its particularities, which in the
case of logic defects (as in the CONFIG_NUMA example presented in Figure 4.1)
also involves analyzing KCONFIG dependencies and further parts of the source
code. This information is then used to develop a patch that fixes the defect. As
a matter of pragmatics, these defects are added into a local whitelist to filter
them out in future runs.
In the period of February to July 2010, 123 patches have been written and
submitted to the Linux kernel mailing list (LKML). For the 1,776 defects found
by UNDERTAKER in Linux 2.6.35, 123 patches for in total 364 configuration
defects were submitted to the upstream kernel maintainers. The breakdown
of the patches is depicted in detail in Table 4.2: 57 patches were explicitly
accepted as email reply or direct GIT commit. These cases classify as confirmed
defect. In 15 cases, the patch is rejected because the maintainer says that the
code is actually used. In 15 further cases the maintainers acknowledged the
problem and had some other solution already queued up or disagreed with
removing the patch for some other reason. These cases classify as confirmed
rule violation. For the remaining 36 submitted patches the outcome cannot be
determined with absolute certainty, because the patch submission received no
76
4.1. Configuration Consistency
Patch status
P
Submitted
Patch accepted
Confirmed rule violation
Problem acknowledged
No Answer
Critical
Non-critical
P
17
106
123
14
1
1
1
43
14
14
35
57
15
15
36
Table 4.2.: Critical patches do have an effect on the resulting binaries (kernel and
run-time–loadable modules). Non-critical patches remove text from the
source code only.
defect
reports
whitelist
filter
defect
analysis
document
in whitelist
submit
patch
upstream
reject
rule violation
improve
and
resubmit
accept
confirmed bug
Figure 4.5.: Basic workflow with the UNDERTAKER tool. After analyzing a defect report,
a patch is manually created and submitted to kernel developers. Based on
the acceptance, the defects that UNDERTAKER reveals are classified either
as confirmed rule violation or confirmed defect.
reply at all or the received response (2 cases) regarded only the formatting
but not the content of the change.
The submitted patches focus on the arch/ and driver/ subsystems and
fix 364 out of 1,776 identified defects (20%). 23 (6%) of the analyzed and fixed
defects were classified as bugs. When extrapolating this defect/bug ratio to the
remaining defects, another 80+ configurability-related bugs can be expected
to be present in this version of the Linux kernel.
In general, the patches are well received: 87 out of 123 (71%) have been
answered; more than 70 percent of them within less than one day, some
even within minutes (Figure 4.6). This indicates that many of the patches
are easy to verify, and in fact appreciated. Moreover, Table 4.2 shows that
the responsible Linux developers consider them as worth fixing: 16 out of 17
(94%) of the critical patches have been answered; 9 have already been merged
into Linus Torvalds’ master git tree for Linux 2.6.36.
The majority of the patches fix defects that affect the source code only, such
as the examples shown in Section 4.1.1. Even for these non-critical patches,
77
Chapter 4. Case Studies
Response within:
< 1 hour
< 1 day
< 1 week
28.74%
72.41%
90.8%
Figure 4.6.: The responsible code maintainers respond to majority of the submitted
patches very promptly.
Configuration Defects
ϕCPP Defects (dead):
ϕCPP Defects (undead):
Logic Defects (dead):
Logic Defects (undead):
Referential Defects (dead):
Referential Defects (undead):
Total defects:
without
file constraints
with
file constraints
change (+/-)
2,402
58
109
8
569
4
2,402
50
420
10
540
4
0%
−13.8%
285.3%
25%
−5.1%
0%
Σ 3,150
Σ 3,426
8.8%
Table 4.3.: Results of the configuration consistency analysis on Linux v3.2
57 out of 106 (54%) have already reached the acknowledged state or later, as
depicted in Figure 4.5. These patches clean up the kernel sources by removing 5,129 lines of configurability-related dead code and superfluous #ifdef
statements (“cruft”). This is a strong indicator that the Linux community is
aware of the negative effects of configurability on the source-code quality, and
welcomes attempts to improve the situation.
4.1.4. Improvements by Makefile Constraints
A considerable limitation of UNDERTAKER version 1.1 is that this version does
not consider the constraints that stem from the build system K BUILD. This
means that in this case, the implementation variability model ϕImplementation
consists of the CPP constraints ϕCPP as described in Section 3.3 only. Newer
versions of the UNDERTAKER tool integrate the results of GOLEM as described
in Section 3.2, the makefile constraints ϕK BUILD . Therefore, in UNDERTAKER
version 1.3 and later ϕImplementation consists of both ϕCPP and ϕK BUILD .
As Nadi and Holt point out, the makefile constraints allow the identification
of dead source files, that is, source files that will never be compiled due to the
constraints from K BUILD [NH12]. I can confirm with my variability extractor
that the contributions of Nadi and Holt to the responsible kernel maintainers
in form of proposed code changes have fixed all these “dead files”. Additionally,
by considering K BUILD-derived constraints, my UNDERTAKER tool detects 311
additional (+285.3%) logic defects in #ifdef-blocks. The total number of
78
4.1. Configuration Consistency
configuration defects in Linux increases by 10.3 percent. This shows that the
source-file constraints have a considerable improvement on the results: The
majority of logic integrity defects can only be found by considering ϕK BUILD .
In order to show the direct benefits of adding makefile constraints to the
results of the experiments shown earlier in this section, I apply UNDERTAKER
version 1.3 to Linux v3.2 with and without the makefile constraints ϕK BUILD .
In this new experiment, source file constraints from all 22 architectures in
Linux v3.2 have been used to enrich the variability models. Every defect is
tested against each architecture individually (where applicable) and classified
as such.
Table 4.3 summarizes the results of this software experiment. In addition to
the data shown earlier in this section, the results for this experiment includes
configuration defects that manifest independently of KCONFIG constraints
ϕKCONFIG as “ϕCPP defects”. In almost all cases, the experiment considerably
increases the number of found defects. In the cases where the number of
defects decreases, I have manually verified that the defect reports were indeed
reported in error. In this verification, the “missing” defect reports turned
out as false positives. This means that in all cases, the addition of makefile
constraints leads to significantly more precise results.
4.1.5. Accuracy
The acceptance of the proposed by the kernel developers approach depends
on the accuracy of the reports. There are two types of incorrect reports: False
negatives and false positives. False negatives are issues that remain undetected.
False positives are conditional blocks that are wrongly reported as unselectable.
Both kind of defects can happen because of implementation challenges.
By design, the approach operates conceptually exact. This means that
with a correct implementation and exact variability models ϕImplementation and
ϕConfiguration , the UNDERTAKER tool would find all defects. However, since the
exact extraction of constraints from languages without well-defined semantics
is unfeasible, I have decided to be conservative with the chosen subset of
KCONFIG and K BUILD constraints in the extractor as detailed in Section 3.1
and Section 3.2. This avoids false positives (minus implementation bugs) in
favor of false negatives, that is, the rate of the remaining, unidentified issues.
For the intended applications, like the integration into the regular workflow of
a kernel developer, this is acceptable.
4.1.6. General Applicability of the Approach
While the high amount of variability in Linux makes this prominent software
project a natural evaluation subject, the proposed approach applies well to
79
Chapter 4. Case Studies
Configuration Defects
ϕCPP defects
Referential Defects
Logic Defects
Dead
Undead
Total
131
68
3
6
1
1
137
69
4
Total Defects
210
(a) Results with B USY B OX version 1.20
Configuration Defects
ϕCPP defects
Referential Defects
Logic Defects
Total Defects
Dead
Undead
Total
93
628
346
5
14
6
98
642
352
1,092
(b) Results with C OREBOOT version 4.0-2866-gbef3d34.
Table 4.4.: Further configuration consistency analyses
other code bases as well. This allows to evaluate the accuracy and scalability of
the UNDERTAKER approach and implementation on other software families, as
long as there is some way to extract the configurability constraints ϕImplementation
and ϕConfiguration . These properties hold for instance on the projects B USY B OX
and C OREBOOT.
The B USY B OX software project was already introduced in Section 3.2.7.2. As
B USY B OX uses KCONFIG and a K BUILD-like build system, the same variability
extractors that I have used on Linux in this subsection could successfully
extract 3,316 variation points from 524 source files in B USY B OX version 1.20.
Table 4.4a breaks down the results in more detail.
Next, I apply my approach on C OREBOOT version 4.0-2866-gbef3d34. C ORE is an open-source firmware implementation that is able to replace the
system BIOS on many mainboards. Similar to B USY B OX and Linux, C OREBOOT
employs KCONFIG for managing the 1,454 configuration items in this version
(the most important item chooses one out of 178), which allows me to extract
variability information from the 4,138 variation points that are spread over
2,796 source files. Table 4.4b depicts the results of this experiment.
BOOT
On these projects, my UNDERTAKER prototype reveals 210 configuration
defects in B USY B OX and 1,092 defects in C OREBOOT that need to be further
investigated by the respective maintainers.
80
4.1. Configuration Consistency
4.1.7. Further Related Work
Consistency checks in variability models have also been investigated by Czarnecki and Wasowski [CW07]. In that work, the authors present an approach
to transform logic formulas into feature models. With this as theoretical basis,
the same group of researchers extends this work to show the semi-automatic
reverse engineering of feature models from source code [She+11]. In contrast,
the UNDERTAKER approach does not require manual interaction for extracting
the variability models. Generalized feature models [CW07], that is, feature
models without domain-specific structural ordering, are sufficient in this context. However, those works extract their propositional formulas from KCONFIG
alone, the constraints from makefiles and #ifdef expressions remain out of
scope.
Benavides, Ruiz-Cortés, and Trinidad present several techniques for reasoning on feature models after transforming them into Boolean formulas [BRCT05].
Combined with the work on reverse engineered feature models by She et al.
[She+11], these reasonings could be integrated into UNDERTAKER to enhance
the static analysis.
Crosschecking between different variability models is also related. Metzger
et al. present several formalizations that allow for consistency checks between
variability models [Met+07]. Czarnecki and Pietroszek [CP06] present an
approach to checking the consistency of feature-based model templates, that
is, checking consistency of feature models and model templates. However, all
these approaches are hard to apply to system software such as Linux, which
uses the C preprocessor and build systems based on makefiles.
4.1.8. Summary: Configuration Consistency
The holistic view on configurability in Linux allows revealing software defects that arise from inconsistencies between the implementation models
ϕImplementation and ϕConfiguration . The extractors presented in Chapter 3 provide
the basis for building the holistic model ϕ that allows checking each #ifdef
block in the code for configuration defects.
The case study in this section reveals that Linux version 3.2 contains 3,426
integrity violations in total, many of which indicate serious issues in the
implementation. My prototype implementation in form of the UNDERTAKER tool
allows detecting them systematically, which has led to the submission of 123
patches, most of which have been accepted by the upstream Linux developers.
Moreover, the consideration of the makefile constraints ϕK BUILD greatly increases
the results by revealing +285.3% additional logic configuration defects.
These results show that the approach implemented by the UNDERTAKER
tool is effective for finding configuration inconsistencies. How and why these
inconsistencies are generally introduced remains a topic for further research
81
Chapter 4. Case Studies
(such as e.g. [Nad+13]), which will allow to design and integrate preventive
mechanisms in the development workflow. Hereby, the proposed variability
extractors and the UNDERTAKER tool will be instrumental.
82
4.2. Configuration Coverage
4.2. Configuration Coverage
The holistic variability model ϕ, as constructed by the extractors presented
in Chapter 3, is useful to improve the effectiveness of tools for static analysis.
Such tools provide a much-welcomed approach to reveal implementation
problems in the source code without the need to actually run the code, and
can already be employed in the implementation phase.
There is a large number of both, commercial as well as academic tools,
that are available to help programmers (e.g., [Cho+01; Eng+01; Tan+07;
Kre+06]). In academic papers, one often reads statements from authors like
“we have tested this with Linux, it works.” or “Our tool has found 47 bugs in
Linux!”. Unfortunately, non-homogeneous configuration mechanisms, such
as KCONFIG, K BUILD, and the CPP, limit the coverage of such tools for static
analysis severely, which raises the question of how much of Linux is actually
analyzed. Even though static configurability by conditional compilation is
omnipresent in system software, the code that is not covered by the used
configurations remains unconsidered. This seriously impedes the qualityassurance and can, as shown in this thesis, be mitigated by tool support.
This can be seen both in the available tooling as well in the available literature. Tool support, such as GCOV4 , ensure, for instance, that unit tests reach a
certain coverage criteria. However, the available methods and tools generally
understand coverage from the compiler’s perspective – they (implicitly) assume
that preprocessing and thus, static configuration, has already happened. This
has practical implications on the every-day work of software developers: A
Linux developer, for instance, who has modified a bunch of files for some
maintenance task, would probably want to make sure that every edited line
of code does actually compile and has been tested before submitting a patch.
However, deducing a set of configurations that covers all relevant compilationconditional parts of the source is a sometimes difficult, but always tedious and
error-prone task.
4.2.1. Configurability versus Static Analysis
Consider the following example of a variation point that is implemented with
CPP and appears in various places in the Linux kernel:
#ifdef CONFIG_NUMA
Block1
#else
Block2
#endif
Depending on the configuration switch CONFIG_NUMA, either Block1 or Block2
is passed to the compiler (or any other static checker that gets used as a
4
GCOV
is a program for assessing the test coverage, which is included in the GNU Compiler Collection.
83
Chapter 4. Case Studies
compiler replacement). This means that the responsible maintainer has to
derive at least two configurations to validate that each line of code does even
compile. It is not hard to imagine that doing this manually does not scale to
the size of real-world system software, such as Linux.
To illustrate this issue with a realistic example, consider a Linux developer
who has modified a bunch of files and is about to submit the resulting patch
to a kernel maintainer, and then asks to integrate the contribution. At this
point, he wants to make sure that every changed line of code does actually
compile and has been tested. The challenge is to figure out the required set of
configurations.
To show how this problem manifests in practice, consider the following
situation in the HAL for the ARM architecture in Linux. In the file arch/
arm/march-bcmring/core.c, the timer frequency depends on the chosen
derivative, as configured in KCONFIG:
#if defined(CONFIG_ARCH_FPGA11107)
/* fpga cpu/bus are currently 30 times slower so scale frequency as well
to slow down Linux’s sense of time */
[...]
#define TIMER3_FREQUENCY_KHZ (tmrHw_HIGH_FREQUENCY_HZ/1000 * 30)
#else
[...]
#define TIMER3_FREQUENCY_KHZ (tmrHw_HIGH_FREQUENCY_HZ/1000)
#endif
The header file tmrHw_reg.h defines the variable tmrHw_HIGH_FREQUENCY_MHZ
with the value 150,000,000 to denote a frequency of 150 MHz. These timer frequencies are used in the static C99-initialization of the timer sp804_timer3_clk:
static struct clk sp804_timer3_clk = {
.name = "sp804-timer-3",
.type = CLK_TYPE_PRIMARY,
.mode = CLK_MODE_XTAL,
.rate_hz = TIMER3_FREQUENCY_KHZ * 1000,
};
The problem at hand is that the member rate_hz, which has the type unsigned
long (i.e., 32 Bits on Linux/arm), is too small to contain the resulting value
of 30 times 150 MHz in Hertz. This unexpected integer overflow leads to the
issue that the timer on the FPGA11107 derivative will operate at a much lower
frequency than expected.
The point here is: This is a configuration-dependent bug that is easy to
detect at compile time! The compiler GCC correctly reports this situation
(with an integer overflow warning), if and only if the KCONFIG selection
happens to cover the feature CONFIG_ARCH_FPGA11107. However, neither the
configuration preset for selecting the maximum configuration (also known as
allyesconfig) which tries to enable all functionality in Linux, nor any other
standard configuration, reveals this bug, which presumably is the reason why
84
4.2. Configuration Coverage
this bug remained unnoticed for three years.
This issue is an example of what my approach is able to reveal, and has been
reported to the responsible maintainer, who promptly acknowledged it as a
new bug.5
The bottom line of this story is that maximizing the analyzed code by
selecting the allyesconfig configuration preset cannot handle alternative
code implementations like the presented #else block shown above. Such
configuration-dependent bugs are often easy to find with existing static code
checkers. However, many of such bugs are hidden in the mechanics of the
variability implementation. The systematic calculation, and subsequent testing,
is a promising method to cover the whole code base by existing code scanners,
in this case GCC.
Even though static configurability is omnipresent in system software, the
scientific community seems to be somewhat agnostic of variability caused by
configurability: Many papers, such as the aforecited ones, have been published
about applying static bug-finding approaches to Linux and other pieces of
system software. In all cases, the authors could find a significant number
of bugs. It is, however, remarkable, that the issue of configurability is not
mentioned at all in these papers; in most cases the authors do not even state
how many or which configurations they have analyzed. This does not only
raise strong issues with respect to scientific reproducibility, but also potentially
limits their success. The common approach apparently is to use a predefined
configuration on a single architecture only [Pal+11]6 . In the case of Linux,
this typically is what today is called allyesconfig. Moreover, private
communication reveals that with Linux, the common approach is to use a
standard configuration. This is perfectly acceptable – their goal is to find bugs
and they have found many of them [Bes+10]. However, how many additional
bugs could possibly be found with full coverage? Given that allyesconfig is
the de facto standard configuration preset for testing things with Linux: What
is its actual coverage, and how much code remains uncovered? And most
importantly, what can be done to maximize configuration coverage (CC) most
efficiently, ideally with reusing existing tools?
4.2.2. Configuration Coverage in Linux
In order to assess how much code is left uncovered, it is necessary to consider
how much configuration-dependent code is actually covered by a single configuration. The metric for calculating the CC operates on variation points that
5
6
https://lkml.org/lkml/2012/4/23/229
In their “Ten years later” paper, Palix and colleagues describe the enormous difficulties to figure out
the Linux v2.4.1 configuration used by Chou et al. in [Cho+01] in order to reproduce the results.
Eventually, they had to apply source-code statistics to figure out the configuration “that is closest to
that of Chou et al.” [Pal+11].
85
Chapter 4. Case Studies
represent the selection (or deselection) of #ifdef blocks and conditionally
compiled source files.
The CC depends on the chosen coverage criteria, such as statement coverage
(every block is included at least once), decision coverage (every block is included
and excluded at least once), and path coverage (every possible combination of
blocks is included at least once). In this thesis, I go for statement coverage and
define the configuration coverage (CC) of a given configuration as the fraction
of the thereby selected blocks divided by the number of available blocks:
Definition 2 The configuration coverage of a given configuration is the fraction of the thereby selected blocks divided by the number of available blocks:
CC :=
selected blocks
available blocks
(4.2)
The number of selected blocks in a configuration is determined most effectively by establishing the presence condition (PC) of each block, which comes
in form of a propositional formula. The selection of a block is then the result
of solving a satisfiability problem, for which there are very fast SAT solvers
available7 . The PC of a block can be easily determined using the holistic
variability model ϕ, which I have presented in detail in Chapter 3.
4.2.2.1. Normalized Configuration Coverage
In practice, however, also the set of available blocks cannot be determined from
the source code alone. The dominance hierarchy (cf. Section 2.1.3), which
results from the order in which KCONFIG, MAKE, and CPP are orchestrated
during the build phase, imposes additional constraints. On each level, the
variability constraints given on this level effectively restrict the variation space
on all lower levels. This generally leads to the fact that an analysis with a
set of choices on an upper level, such as analyzing a specific architecture
(e.g., choosing Linux/arm on Level l0 ), will lead to variation points on a lower
level, such as CPP blocks in a file (Level l2 ), that are only seemingly variable.
These seemingly variable blocks correspond to the dead and undead blocks as
described in Section 4.1.1. The difference is that in this context, these “dead”
blocks are not (necessarily) dead on all, but only on the currently analyzed
architecture.
To illustrate the effects of this dominance hierarchy in practice, consider the
following excerpt from drivers/net/ethernet/broadcom/tg3.c:
static u32 __devinit tg3_calc_dma_bndry(struct tg3 *tp, u32 val)
{
int goal;
7
For this, I again employ the
86
PICOSAT
implementation by Biere [Bie08].
4.2. Configuration Coverage
[...]
#if defined(CONFIG_PPC64) || defined(CONFIG_IA64) ||defined(CONFIG_PARISC)
goal = BOUNDARY_MULTI_CACHELINE;
#else
#if defined(CONFIG_SPARC64) || defined(CONFIG_ALPHA)
goal = BOUNDARY_SINGLE_CACHELINE;
#else
goal = 0;
#endif
#endif
This code configures architecture-dependent parts of the Broadcom TG3 network device driver with #ifdef blocks. However, given any concrete architecture (which is the common perspective in Linux development), these blocks
are not variable: The PCs of the #ifdef blocks will either result in a tautology (undead on the respective architectures) or a contradiction (dead on all
other architectures). For instance, the first #ifdef block is selected for compilation if and only if the code is compiled on Linux/ppc64, Linux/ia64 or
Linux/parisc. On these architectures, the PC will result true for any KCONFIG
configuration and false on any other architecture. From the perspective of, for
instance, a platform maintainer on Linux/arm, these blocks are only seemingly
variable: Independently from the configuration, the second #else block is
always selected. However, on Linux/s390 the PCs of all blocks (including the
second #else block) result in a logical contradiction, as the complete file is
singled out by a constraint given on the KCONFIG level.
The practical consequence is that dead and undead blocks have to be singled
out for calculating the configuration coverage – undead blocks are covered
by every configuration and dead blocks cannot be covered by any. From
the perspective of a platform maintainer, for instance, ignoring these blocks
perfectly makes sense: Linux is generally compiled natively (that is, not using
a cross compiler) and code parts for a foreign architecture are likely to not
compile anyway.
The general lesson to be learned here is that the configurability implemented
by some source file cannot be determined (and, thus, checked) by analyzing
the source code alone, but requires all constraints from KCONFIG, K BUILD, and
the CPP which makes it difficult to integrate configuration coverage into static
bug-finding tools. These insights allow defining a more strict variant of CC
that excludes the only seemingly variable blocks (as illustrated in the example
above) from the calculation:
Definition 3 The normalized configuration coverage (CCN ) is defined as:
CCN :=
selected blocks − undead blocks
all blocks − undead blocks − dead blocks
(4.3)
87
Chapter 4. Case Studies
Architecture
#files
Total
kLOC
in
#ifdef
blocks
# variation points
(dead/undead
rate)
allyes
allyes
CC
CCN
arm
hardware
software
13,159
10,412
2,747
8,568
6,629
1,938
4.6%
3.9%
6.9%
27,348 (18%)
20,021 (20%)
7,327 (11%)
49.2%
40.8%
74.8%
59.9%
51.2%
83.6%
x86
hardware
software
11,862
9,115
2,747
8,391
6,417
1,974
4.5%
3.6%
7.6%
25,114 (17%)
17,188 (22%)
7,926 (4%)
65.2%
59.7%
80.2%
78.6%
76.8%
82.7%
m32r
hardware
software
11,393
8,646
2,747
4,044
2,183
1,860
4.1%
2.5%
6.1%
12,501 (56%)
5,783 (72%)
6,718 (18%)
33.4%
17.9%
71.9%
76.4%
63.5%
87.4%
s390
hardware
software
11,459
8,712
2,747
2,783
1,034
1,748
5.7%
2.8%
7.3%
9,575 (67%)
2,823 (86%)
6,752 (18%)
24.2%
5.1%
71.8%
72.1%
37.2%
86.8%
Mean µ
hardware
software
11,589
8,842
2,747
6,447
4,561
1,886
43.9%
32.2%
73.6%
71.9%
60.9%
86.7%
Std. Dev. σ
hardware
software
±383
±1,736
±66
±1,652
±1,620
±53
±11.8%
±15.8%
±3.5%
±12.2%
±19.6%
±2.6%
...
< 20 further architectures >
3.8%
2.8%
6.4%
17,931 (39%)
10,980 (48%)
6,952 (15%)
±4,414
±1,159
±0
Table 4.5.: Quantification over variation points across selected architectures in Linux
v3.2 and the corresponding CC and CCN of allyesconfig. Numbers
shaded in red/blue represent the maximum/minimum value in the respective column, missing maxima/minima occur in one of the omitted 20
further architectures.
4.2.2.2. The Configuration Coverage of ‘allyesconfig’ in Linux
As already indicated, traditional tools for static analysis in the form of checkers
and bug-finding tools are generally applied to a single configuration only, such
as the configuration preset defconfig, which sets all configuration items to
its default settings, or allyesconfig, which tries to enable all configuration
items. This raises the question how many conditional blocks are commonly
left uncovered – with the consequence that bugs, such as the integer overflow
issue from page 84, remain undetected for several years.
I evaluate the configurability-related source code metrics together with the
resulting CC and CCN of the allyesconfig standard configuration for 24 out
of the 27 Linux architectures.8 Table 4.5 lists selected “typical” architectures
(the relevant extrema values are shaded), together with the mean µ and
standard deviation σ over all 24 analyzed architectures. The table further
discriminates the numbers between “hardware related” (originated from the
subdirectories drivers, arch, and sound) and “software related” (all others,
8
I could not retrieve results for Linux/um, Linux/c6x, and Linux/tile, as they seem to be fundamentally broken in this version of Linux.
88
4.2. Configuration Coverage
especially kernel, mm, net).
The average Linux architecture consists of 9,216 kLOC distributed over 11,488
source files, with 4.6% of all code lines in (real) #ifdef or #else blocks and
28,926 total variation points (#ifdef blocks, #else blocks, and configurationdependent files). There is relatively little variance between Linux architectures
with respect to these simple source-code metrics, as all architectures share
a large amount of the source base (including the kernel and device drivers).
Linux/arm is the largest architecture in terms of number of files and variability.
For the three rightmost columns, however, the variance is remarkable. The
rate of dead/undead variation points varies from 17 percent on Linux/x86
to up to 68 percent on Linux/s390 (µ = 38%). The latter is caused by many
dead device drivers (nearly 90% of all hardware-related variation points are
dead on this architecture): As a typical mainframe architecture, the s390
hardware does not feature the PCI family of buses, for which the vast majority
of Linux device drivers have been developed.
The dead/undead rate is reciprocally correlated to the configuration coverage of allyesconfig, which is highest on Linux/x86 with 65.2 percent
and lowest on Linux/s390, where only 24.2 percent of all variation points are
covered by allyesconfig (µ = 43.9, σ = 11.8). These numbers underline
the necessity to normalize the configuration coverage with respect to the actual
viewpoint (usually the architecture), that is, to incorporate not only the source
code, but all levels of variability implementation as described in Section 2.1.3.
The normalized configuration coverage CCN is generally much higher (µ =
71.9, σ = 12.2) – here Linux/s390 (72.1%) is even above the average and
close to Linux/x86 (78.6%).
The situation is different on typical embedded platforms, where the normalized configuration coverage (CCN ) of allyesconfig is significantly lower:
On Linux/m32r, an embedded processor platform used in engine control units
and digital cameras, only 76.4 percent of the available variation points are
covered, which is the minimum among all Linux architectures. On Linux/arm,
the largest and most quickly growing platform, only 59.9 percent are covered.
These numbers are especially influenced by the relatively low CCN achieved
in the hardware-related parts. Apparently, (typically) embedded platforms
feature a larger mount of variability, which manifest in many alternative or
conflicting features on Level l1 (KCONFIG) and in #else blocks on Level l3 .
4.2.2.3. Related Approaches
Basically, there are two different approaches to increase the configuration
coverage for static analysis. The first one is to extend the parser in a way
that allows it to understand the variability mechanisms. In practice, this
means to parse both branches of all #ifdef statements. Such variability aware
89
Chapter 4. Case Studies
parsers have been proposed for example in form of T YPE C HEF by Kästner et al.
[Käs+11] or S UPER C by Gazzillo and Grimm [GG12]. While both tools use
different parsing approaches, the basic idea remains the same: Instead of a
stream of tokens, these tools produce a directed, acyclic graph in which each
node with outgoing edges represents an #ifdef statement and nodes with two
incoming edges represents an #endif statement.9
This approach has two serious problems: Firstly, not all possible branches
that CPP allows can be actually configured with KCONFIG. This is not only a
performance problem (because many branches are analyzed unnecessarily),
but also means that variability-aware static analysis still requires a cross-check
to the configuration model, such as the one presented in Chapter 3 of this
thesis, to ensure that found errors actually occur in practice. Secondly, this
approach requires a major re-engineering of the static analyses. The lack of
industry-grade tools, which would be suitable for production use, indicates
that this requires significant effort.
As a pragmatic approach, I suggest to use existing analysis tools with a small
set of configurations instead, and invoke the tool on each configuration to
maximize the configuration coverage. This solves both problems: Firstly, by
construction only valid configurations are analyzed, which means that for each
found defect my approach always provides the corresponding configuration
and thus, a test case. Secondly, programmers can reuse proven tools for static
analysis that they are familiar with. Moreover, developers do not need to
learn how to handle any additional tools: The resulting configurations, which
reveal broken code, can be loaded with the existing configuration and build
infrastructure.
4.2.3. Partial Configurations
In this thesis, I address the configuration coverage issue by calculating a set
of configurations based on the analyzed source file. In practice, no source
file in Linux references all available configuration items, but only a strict, and
significantly smaller, subset. This leads to the following definition:
Definition 4 A partial configuration specifies the selection of a strict subset of
all configuration options. The remaining, unspecified options are still subject to
the logical constraints imposed by the configuration model.
In the Linux development community, partial configurations are sometimes also called KCONFIG fragments. The most important use cases are
pre-configurations for embedded development boards that provide a starting
point for the configuration of specific evaluation boards or similar development
9
This characterization is slightly simplified.
90
4.2. Configuration Coverage
targets, such as implemented in the Yocto project [Har13]. Another use case is
to provide configuration policies for features that need to be kept consistent
across a set of configurations for different platforms and similar.
Correctly handling partial configurations is important because most source
files in Linux reference only at most three CPP identifiers. To illustrate,
consider the following excerpt of the source file drivers/char/nvram.c
of Linux v3.2, for which the configuration-conditional #ifdef directives are
depicted in Listing 4.4.
/*
* CMOS/NV-RAM driver for Linux
[...]
*/
[...]
#ifdef CONFIG_PROC_FS
static void mach_proc_infos(unsigned char *contents, struct seq_file *seq,
void *offset);
#endif
[...]
#ifndef CONFIG_PROC_FS
[...]
#endif
[...]
Listing 4.4: Configuration-conditional #ifdef blocks for drivers/char/nvram.c,
Linux v3.2
In this example, the variability implemented on this file has exactly one variation point: the CPP identifier CONFIG_PROC_FS, which is controlled by the
KCONFIG feature PROC_FS (declared with type boolean in fs/proc/Kconfig).
The complete variability can be expressed by exactly two partial configurations: CONFIG_PROC_FS=y and CONFIG_PROC_FS=n. Such (partial) configurations, which reference only a very small subset of all declared features, are
typical for almost all files in Linux. The point here is that in many cases,
several partial configurations need to be combined. As one can easily imagine,
combination of arbitrary partial configuration can lead to self-contradictory
presence conditions.
Partial configurations are not only relevant to Linux kernel developers, but
also in the context of Linux distributions. Here, they help to organize the Linux
kernel configuration for all architectures of a Linux distribution (e.g., Debian
supports more than 10 ports) in a topic-organized way. This allows separating
architecture specific kernel options from common configuration options that
are shared across many architectures, like available filesystems or networking
options.
When assembling a full Linux configuration using two or more partial
configurations, an important problem is the question whether the involved
partial configurations are consistent with each other:
91
Chapter 4. Case Studies
Definition 5 Two partial configurations are consistent with each other if and
only if there is at least one complete configuration that a) is valid to the configuration model and b) satisfies the selection of all configuration options that each
configuration specifies.
With a configuration model ϕ, such as the holistic variability model presented
in Chapter 3, the question whether two configurations are consistent can be
formulated as a Boolean satisfiability problem, which again, can be passed to
a SAT solver for checking.
A practical problem is that KCONFIG (and similar configuration tools such as
eCosConfig, etc.) are generally not able to load partial configurations reliably.
In order to obtain a configuration that can be loaded, modified and shared
among developers, a partial configuration needs to be expanded:
Definition 6 Finding a configuration that specifies all configuration items in a
way that is consistent to some given partial configuration is called expansion.
In some cases, this expansion process can be implemented with the configuration tool itself. This applies for instance to KCONFIG. There are several
strategies for expanding partial configurations that differ in the policy how to
set the unspecified options. The policy of the alldefconfig strategy is to match
the default setting of the configuration model. Another policy is allyesconfig
(allnoconfig), which tries to enable (disable) as many features as possible. In
practice, these policies cannot be enforced for all options because of declared
feature constraints. The resulting conflicts therefore need to be resolved in a
way that results in a configuration that fulfills all feature constraints, and is
still consistent to the partial configuration.
4.2.4. Approach for Maximizing the Configuration Coverage
The definition of configuration coverage (CC) goes with the traditional understanding of code coverage for software testing, but is applied to the operation
of the build system and the CPP. Technically, the CPP-statements of software written in the programming language C describe a meta-program, which
is executed by the C preprocessor before the actual compilation by the C
compiler takes place. In this meta-program, the CPP expressions (such as
#ifdef– #else– #endif) correspond to the conditions in the edges of a loopfree10 control flow graph; the thereby controlled fragments of C-code (i.e., the
bodies of #ifdef-blocks) are the statement nodes. Relevant for the CC are
configuration-conditional block only.
The CC depends on the chosen coverage criteria, such as statement coverage (every block is included at least once), decision coverage (every block
10
Leaving aside “insane” CPP meta-programming techniques based on recursive #include
92
4.2. Configuration Coverage
φ∧ φ ∧ φ
CPP
Block 1
#ifdef CONFIG_X86
<...>
Block 2
#elif CONFIG_ARM
<...>
#endif
Linux
source
Kconfig
undertaker
Kbuild
establish PC
for Block 1
φ
φ ∧φ ∧ φ
CPP
CPP
Kconfig
undertaker
Kbuild
_______
_______
_______
_______
Kconfig
_______
_______
configurations
_______
_______
establish PC
for Block 2
Figure 4.7.: Maximizing CC at a glance: For each #ifdef block in a source file, the PC
serve as basis for the derivation of a set of configurations that maximize
the CC.
is included and excluded at least once), and path coverage (every possible
combination of blocks is included at least once). This thesis targets statement
coverage, although higher order coverage criteria are also thinkable. As already
indicated, the approach to maximize the CC is to find a set of configurations
that, when applied sequentially, selects all blocks in a given source file.
As part of this thesis, I have implemented and evaluated two algorithms that
implement such heuristics and have a complexity of O(n) and O(n2 ). This is
good enough in practice and scales up to the size of Linux. The operation of
both algorithms is explained in detail in Appendix D.
These two algorithms provide an efficient means to calculate a reasonably small set of configurations that maximize the CC and CCN metrics as
described in Section 4.2.1. Technically, they are implemented in the UNDER TAKER toolchain as additional command-line options. Critical for the validity
of the produced configuration is the holistic variability model ϕ as presented
in Chapter 3. Given a single source file as input, the UNDERTAKER tool calculates (partial) configurations that maximize the configuration coverage for this
specific file. This allows programmers participating in large projects, such as
the Linux kernel, to focus on the specific parts of the kernel that they currently
work on. Figure 4.7 depicts the general workflow.
4.2.4.1. The VAMPYR Driver Tool
In order to provide developers an easy-to-use tool, I have written the VAMPYR
tool as a variability-aware driver that orchestrates the derivation of configuration for source files in Linux and software projects with a similar variability
architecture. The general interaction between the individual tools is depicted
in Figure 4.8. VAMPYR drives the extractors from Chapter 3 to ensure that
all constraints from CPP, K BUILD and KCONFIG are available as propositional
formula ϕ. These logical constraints are loaded (UNDERTAKER in Figure 4.8)
93
Chapter 4. Case Studies
config HOTPLUG_CPU
bool "Support for ..."
depends on SMP && ...
KConfig
files
Calculate Configurations
that Maximize
Configuration Coverage
Scan each Configuration
With One or More of:
coccinelle
clang
sparse
gcc
#ifdef CONFIG_X86
<...>
#elif CONFIG_ARM
<...>
#endif
Linux
source
PresenceCondition(b1)
&&
PresenceCondition(b2)
&&
...
establish
propostional
formulas
undertaker
_______
_______
_______
_______
_______
_______
_______
_______
_______
_______
_______
_______
partial configurations
for each
Kconfig
obj-$(CONFIG_HOTPLUG_CPU) = hotplug.o
Make
files
Expand
_______
_______
_______
_______
Kconfig
configuration
Figure 4.8.: Workflow of the VAMPYR configuration-aware static-analysis tool driver
and used to produce the configurations using the algorithms discussed the
previous subsection. VAMPYR then drives the tools for static analysis on each
of these configurations individually.
However, since the resulting configurations only cover variation points
that are included in the source file, they cannot be loaded directly into the
KCONFIG configuration tool. This means that a produced configuration does not
constrain the selection of the remaining, thousands of configuration options
that need to be set in order to establish a full KCONFIG configuration file that
can be shared among developers. These remaining, unspecified configuration
options can be set to any value as long as they do not conflict with the
constraints imposed by the partial configuration.
To derive configurations that can be loaded by KCONFIG, I reuse the KCONFIG
tool itself to expand the partial configurations, in order to set the remaining,
unconstrained configuration options to values that are not in conflict with
ϕKCONFIG (the expansion process is explained in more detail in Section 4.2.3).
With these full configurations, the K BUILD build system applies the tools for
static analysis on the examined file. So far, I have integrated four different tools for static analysis: GCC, SPARSE [Tor03], CLANG (the C front-end
of LLVM [LA04]), and SPATCH from the coccinelle tool-suite [Pad+08]. The
necessary extensions to support the build system of other systems are straightforward to integrate.
94
4.2. Configuration Coverage
4.2.4.2. A Day in the Life of a Subsystem Maintainer
To illustrate the expected use of the approach in practice, the following commands demonstrate a short usage session of a fictional typical Linux developer.
The Linux maintainer for the bcmring ARM development board receives a
contributed patch via email. After a first review, he notices that the patch introduces a number of #ifdef blocks to the file arch/arm/march-bcmring/core.c,
which is only compiled if the relevant KCONFIG options are selected.
In order to systematically compile each line of code at least once, he first
applies the patch to his local GIT tree and then runs the VAMPYR tool on all
files that are touched by the proposed change:
$ git am bugfix.diff
$ vampyr -C gcc --commit HEAD
# Apply the patch
# Examine the latest commit
VAMPYR derives a CC-maximizing set of configurations (about 1.2 configurations per file in average) for each file that is mentioned in the patch. The
resulting configurations are plain text files in a syntax that is familiar to Linux
developers, but only cover those variation points that are actually related to
the PCs contained in the affected source files. Therefore, VAMPYR utilizes the
KCONFIG configuration tool to derive configurations that set all remaining,
unspecified items to the default values. The expanded configuration is activated in the source tree (i.e., K BUILD updates the force-included autoconf.h
and auto.make files), and GCC, or another static checker, is called on the
examined file.
The issued warnings and errors are presented to the developer after VAMPYR
has inspected all configurations. The whole process takes less than a minute
on a modest quad-core development machine. In this case, VAMPYR reveals
the integer overflow that has been presented in Section 4.2.1.
The same maintainer implements a nightly quality-assurance run. After
having integrated the submissions of various contributors, he calls it a day and
lets VAMPYR check the complete source code base on Linux/arm (a work list
with 11,593 translation units) in a cronjob:
$ vampyr -C gcc -b worklist
Here, VAMPYR takes about 2.5 hours on a 16-core server machine, which
includes the time necessary for extracting the variability from KCONFIG and
K BUILD. I have summarized the findings of such a run in Table 4.6.
Both application examples indicate that developers can employ the approach
at the implementation and integration phases. This helps both, subsystem
maintainers that review contributions, and programmers of device drivers
who are generally expected to at least verify the code by compiling it for all
relevant KCONFIG options. The approach results in a straightforward and easy
to use tool that unburdens developers from the tedious task of finding (and
testing) the relevant configurations systematically.
95
Chapter 4. Case Studies
4.2.5. Experimental Results
I show the effectiveness of the approach with its application on two operating
systems, namely Linux version v3.2 and F IASCO, as well as on B USY B OX, a
versatile user-space implementation of important system-level utilities targeted
at embedded systems, which has been introduced in Section 3.2.7.2). They
all use only slightly different versions of KCONFIG, which allows reusing the
variability extractor for ϕKCONFIG for all projects. Based on the observations
on the CC and CCN metrics in Section 4.2.2, the analysis focuses on two
architectures: Linux/arm, the largest and most quickly growing one with the
lowest CCN , and Linux/x86, the oldest and presumably best tested one with
an above-average CCN . In all cases, the set of configurations is calculated for
each file. As a static checker, this evaluation employs GCC v4.6.1 for each
configuration on all files individually. As an optimization, the initial starting
set contains the standard configuration allyesconfig.
Unfortunately, KCONFIG overrides in some cases the actual features of interest when expanding the partial configuration to a full configuration, which has
a significant impact on the resulting CCN . In order to achieve correct results,
VAMPYR validates each configuration after the expansion process: Configurations that do no longer contain the relevant features of interest after expansion
are skipped; the therein contained #ifdef blocks therefore do not contribute
to the CCN . As a result, the achieved CCN in practice is still below one hundred
percent. This, however, is caused by deficiencies of the employed tools and
does not invalidate the approach itself.
4.2.5.1. Application on Linux
Table 4.6 depicts the results for the architectures Linux/arm and Linux/x86.
The table shows CCN and found issues, like described in Section 4.2.2, separated by hardware- and software-related parts. For both architectures, there is
an increase in average of the CCN by more than 10 percent, while requiring
about 20 percent more run time in form of GCC invocations compared to a
regular compilation with a single configuration. Most files in Linux achieve
full CC with a single configuration, but not all of them are covered by the
standard configuration allyesconfig, especially not on Linux/arm.
The result column of Table 4.6 shows that VAMPYR reveals 199 additional
warning and error messages that are not found with the allyesconfig
configuration on Linux/arm and 26 additional messages on Linux/x86. Interestingly, the rate of GCC reported issues varies significantly among the
different subdirectories. When taking the number of #ifdef blocks per reported issue as a metric for code quality, for both Linux/arm and Linux/x86
the software-related code contains significantly less issues per #ifdef block
than the hardware related-code. For instance, the subdirectory net/, which
96
blocks per re-
VAMPYR
46
34
144
27
96
software
kernel
net/
rest
83.6%
76.9%
92.6%
80.9%
96.3%
94.4%
99.7%
91.4%
19.5%
22.7%
15.3%
19.8%
37 (32)
13 (10)
24 (22)
0 (0)
0 (0)
0 (0)
0 (0)
0 (0)
37
13
24
0
192
269
123
∞
Linux/x86
hardware
arch/
drivers/
sound/
78.6%
76.8%
46%
80.4%
84.2%
88.4%
86.5%
59.3%
89.8%
92.9%
21.5%
21%
40.5%
18.8%
28.1%
201 (176)
180 (155)
0 (0)
140 (118)
40 (37)
1 (0)
1 (0)
0 (0)
1 (0)
0 (0)
202
181
0
141
40
110
82
∞
85
44
software
kernel
net/
rest
82.7%
78.5%
88.7%
80%
92.4%
91.4%
94.6%
89.1%
22.7%
26.3%
18.8%
21.1%
21 (21)
11 (11)
10 (10)
0 (0)
0 (0)
0 (0)
0 (0)
0 (0)
21
11
10
0
351
342
296
∞
L4/FIASCO
99.1%
74.2%
99.8%
97.3%
see text
20 (5)
44 (35)
1 (0)
0 (0)
21
44
see text
Busybox
ported issue
508
471
17
434
20
#ifdef
92 (15)
92 (15)
5 (0)
85 (15)
2 (0)
Σ Issues
GCC #errors
(allyesconfig)
#warnings VAMPYR
417 (294)
380 (262)
12 (2)
350 (251)
18 (9)
GCC
22.7%
23.7%
31.4%
21%
27.8%
CCN
84.4%
80.1%
49.8%
89.3%
93.6%
VAMPYR
59.9%
51.2%
4.4%
67.2%
60.9%
CCN
Linux/arm
hardware
arch/
drivers/
sound/
Software
Project
allyesconfig
(allyesconfig)
Overhead of additional GCC Invocations per File on average
4.2. Configuration Coverage
Result:
increase of GCC
messages
199 (+64.4%)
26 (+14.8%)
16 (+320%)
9 (+25.7%)
Table 4.6.: Results of the VAMPYR tool with GCC 4.6 on Linux v3.2, F IASCO and B USYB OX.
contains all networking-related code in Linux, is very much independent of
the selected architecture and contains the fewest GCC reported issues per
variation point (1,289 #ifdef blocks per issue on Linux/arm and 2,766 #ifdef
blocks per issue on Linux/x86). By this, I can confirm the observation from
the literature [Cho+01; Ryz+09; Pal+11] that hardware-related code contains significantly more bugs than software-related code also in the context of
variability.
I have reviewed all messages manually to discriminate the messages that
are likely to be uncritical from real bugs. Table 4.7 presents the classification
of the compiler messages on Linux v3.2 Linux/arm, separated by warnings
and errors. It turns out that not all warnings from GCC accurately indicate
bugs in the code. Fortunately, extra compiler flags to GCC can reduce the
number of unhelpful messages. For example, the flag -fno-unused suppresses
97
Chapter 4. Case Studies
warnings about defined but never referenced variables. This diagnostic mode
is enabled by default with GCC v4.6. However, Linux developers consider the
resulting messages generally as not critical. This new GCC compiler version
also introduces a compilation error because of changes in the register allocator
that makes the inline assembler unable to satisfy the specified constraints
in one case. Since this problem did not occur in earlier versions of GCC,
this error classifies as “less critical”. Also, there are 9 compilation warnings
and 2 errors in Linux/arm (using the #warning and #error CPP directives)
that are deliberately added by programmers to work around limitations in
the expressiveness of the KCONFIG language. One could argue that such a
programmatic way to validate a configuration should be addressed in KCONFIG
itself. This would avoid configurations that can be selected in the configuration
tool but do not compile at all. However for the present work, such warnings
also have to be considered as false positives.
In all other cases, the manual validation reveals serious bugs. In 7 cases,
including the issue from Section 4.2.1, I have analyzed the bug in more detail
and proposed a patch to the upstream developers. It turns out that all 7 bugs
remained unnoticed for several years. To illustrate the found issues, I provide
a compilation of the other six bugs with the proposed changes in the Appendix,
Chapter E.
4.2.5.2. Application on L4/Fiasco
The approach is not limited to Linux. In order to show the general applicability,
I apply the VAMPYR tool to the code base of the F IASCO micro kernel. Compared
to Linux with its more than 15 million lines of code [CKHM12], F IASCO with
its about 112 thousand lines of code in 755 files (only counting the core
kernel, i.e., without userspace related packages) is smaller by magnitudes.
Nevertheless, I identify 1,255 variation points (1,228 conditional code blocks
and 16 conditionally compiled source files) in the code base.
The F IASCO developers have imported the KCONFIG infrastructure. The
KCONFIG configuration model allows configuring 157 features on 4 architectures. Unlike in Linux, the architecture is a “normal” user-selectable KCONFIG
option.11 Also, F IASCO does not use the CPP, but a custom preprocessor (called
PREPROCESS ) for implementing conditional compilation. In a nutshell, this
preprocessor provides an additional syntax to allow the programmer to declare
interfaces and implementations in the same source file. The preprocessor then
transforms the source files to the traditional header and implementation files,
and then compiles the kernel with GCC. This transformation step also makes
the metric of GCC invocation per source file impossible to compare to the other
11
In Linux, the architecture is not controlled with KCONFIG. Instead, the environment variable ARCH
instructs K BUILD which of the 22 distinct KCONFIG models to choose. For details, cf. Section 2.1.
98
4.2. Configuration Coverage
Less critical GCC messages
warnings
Deprecation Warnings (__attribute__((deprecated)) ) 127 (89)
Undefined or Redefined CPP Identifiers
17 (2)
Operation on incompatible pointer types
10 (3)
Invalid K CONFIG selections
9 (2)
Suspicious Control Flow
8 (8)
Comparisons between incompatible types
9 (6)
Assembler Failures
Other Messages
18 (11)
Σ Possibly uncritical messages
198 (121)
Manually validated Bugs
Undeclared Types/Identifiers
Access to Possibly Uninitialized Data
Out of Bounds Array Accesses
Warnings about Misaligned Accesses
Format String Warnings
Integer Overflows
Σ Bugs
Σ All reported issues
errors
3 (0)
1 (0)
4 (0)
37 (14)
25 (4)
11 (7)
3 (3)
1 (0)
1 (0)
41 (14)
39 (14)
239 (135)
43 (14)
2 (0)
Table 4.7.: Classification of GCC warnings and errors revealed by the VAMPYR tool on
Linux/arm. The numbers in brackets indicate messages that are also found
when compiling the configuration allyesconfig
projects.
On the transformed F IASCO code base, the VAMPYR tool produces 9 different
configurations which in total cover 1,228 out of 1,239 #ifdef blocks. Thus, the
approach achieves a CC of 99.1% and a CCN of 99.8%.
When compiling the default configuration allyesconfig, there are 5 different compiler warnings. However, when compiling all 9 calculated configurations, the number of observable compiler warnings increases to 20. Additionally, one of the 9 configurations exhibits a compilation error in ux/main-ux.cpp.
In that file, the instantiation of a Spin_lock type lacks a type parameter, which
leads to a compilation failure if the features CONFIG_UX (for choosing Linux
user-mode operation as target) and CONFIG_MP (for multi-processor support)
are enabled. I have reported this issue to the F IASCO developers, who confirmed it as a bug.
4.2.5.3. Application on B USY B OX
Another popular software project that makes use of KCONFIG is the B USY B OX
tool suite, which I have already introduced in Section 3.2.7.2 and provides an
implementation of basic system utilities. The analyzed version 1.20.1 exposes
879 features that allow users to select exactly the amount of functionality that
99
Chapter 4. Case Studies
is necessary for a given use case.
B USY B OX uses a slightly modified version of KCONFIG that produces additional variants of the CPP and MAKE representations of the configuration
options. In the code, the programmer uses these alternative (syntactic) ways
to express variability constraints, as described in Section 3.3.5. This requires
an additional normalization step, which was easy and straightforward to implement. In order to ensure that this normalization step has no impact on the
analysis, all warnings listed in Table 4.7 have been verified manually.
In B USY B OX, the “standard” allyesconfig configuration enables a configuration option that promotes any compiler message as error (i.e., the build
system adds the GCC option -Werror to the compilation flags). In order to
keep the reported issues comparable with all generated configurations, this
option gets disabled after applying the allyesconfig configuration in an
automated fashion. Moreover, the tooling prevents generated configurations,
such as the partial configurations that result from the coverage algorithms, to
enable this option in the first place as an optimization for increasing the CCN
metric further.
In total, there are 3,316 #ifdef blocks and conditionally compiled source
files, from which VAMPYR classifies 1.39 percent as dead and undead. As
Table 4.6 shows, the VAMPYR tool increases the number of GCC reported issues
by over 25 percent.
4.2.6. Discussion
The application of the VAMPYR prototype implementation reveals a number of
issues and bugs in three system software projects. The following discusses the
threats to validity and the general applicability of the general approach.
4.2.6.1. Threats to Validity and Quality of Results
The results are sensitive with respect to several assumptions, which might, if
invalid, impact the validity of the approach.
Does allyesconfig reach the best achievable CC?
The allyesconfig standard configuration is generally supposed to include
almost all of the available code into the resulting compilation products. It
is generated by the KCONFIG tool with a simple algorithm: The algorithm
traverses the feature tree and selects each feature that is not in conflict to an
already selected feature. This has two consequences:
1. The outcome of allyesconfig is not a sensible configuration for any
production system.
100
4.2. Configuration Coverage
2. The algorithm is sensitive to the order of features in the model. It is not
actually guaranteed that the result includes the maximum number of
features.
Also, even when assuming a maximum number of features as the outcome,
this does not necessarily also imply the largest possible CC, as there might
be a feature with a highly cross-cutting implementation (i.e., a feature that
contributes many #ifdef blocks) left disabled in favor of another feature that
contributes just a single variation point. However, because of the KCONFIG
user-interface concept (consistency is guaranteed by showing only those configuration options for which the prerequisites are fulfilled), the feature trees
are generally ordered so that features with a more generic impact (such as enabling support for loadable kernel modules) come before features that depend
on this decision. Hence, conflicts during the allyesconfig traversal process
can be expected to show up on the level of fine-grained features. Moreover,
features in Linux are not very cross cutting: 58 percent of all features in Linux
v3.2 are implemented by a single variation point; only 9 percent of all features
contribute more than 10 variation points. This means that despite these limitations, allyesconfig remains a realistic upper bound of the CC that can
be achieved with a single configuration.
Does K CONFIG expand partial configurations correctly?
In order to allow the resulting configuration to be loaded, saved, and shared
among developers, the resulting partial configurations are expanded with the
Linux KCONFIG tool.
Unfortunately, KCONFIG uses a similar simple algorithm when expanding an
incomplete configuration as used for obtaining the allyesconfig configuration. It therefore cannot be ruled out that some of the partial configurations
might have been expanded incorrectly. Such effects can lower CC and CCN and
leave bugs undetected.
Annoyingly, the KCONFIG tooling does not give any indication when such
inconsistencies occur, so that configuration inconsistencies remain largely
undetected. Recent research, such as by Nöhrer, Biere, and Egyed [NBE12]
promises to reveal (and tolerate) these inconsistencies if the variability model
is encoded in SAT. A proposed Google Summer of Code (GSoC) project12 ,
which suggests to encode the formal semantics of KCONFIG in SAT, indicates
that the Linux developers are open to such initiatives. Unfortunately, as
detailed in Section 3.1, there are a number of peculiarities in the KCONFIG
language that make this translation unnecessarily hard. Therefore, solving
this challenge remains future work.
12
https://lkml.org/lkml/2010/5/17/172 - unfortunately, that work is still in progress at the
time of writing.
101
Chapter 4. Case Studies
Is the variability model ϕ correct and complete?
The quality of the extracted variability constraints may also impact the presented results. Inaccuracies in the extracted variability affect the expansion
process in a negative way and can in some cases lead to self-contradicting partial configurations. Additional technicalities in the KCONFIG implementation
make such inaccuracies subtle in practice: Given a partial configuration that
is not satisfiable within the constraints of ϕKCONFIG , KCONFIG silently overrides
configuration options with different values to what was specified in the partial
configuration. In the best case, this will only impair the CCN of a single file.
However, such incorrectly set configuration options can cause the expanded
configuration to no longer include the file that it was derived from into the
compilation process. In such cases, the analysis does not count any blocks that
the configuration would normally include for the affected files. In essence, this
means that the CCN metrics presented in Table 4.7 are sensitive to the quality
of the extracted variability constraints from KCONFIG features ϕKCONFIG , MAKE
files ϕK BUILD , and CPP blocks ϕCPP , and have to be seen as lower bound that
can be improved by more accurate variability extractors.
Conclusion
To summarize, these threats to validity limit the quality of the results. In fact,
the analysis in Section 4.2.5.1 for Linux/x86 excludes 9% of all calculated
configurations because of such an erroneous expansion. But even with this
quite high number of defective (and therefore omitted) configurations, VAMPYR
still achieves a CCN of 88.4%. The result is a considerable number of source
files and #ifdef blocks that remain uncovered. I am convinced that with an
accurate variability extraction, full CCN can be achieved. When assuming a uniform distribution of detectable bugs, a full CCN would reveal about 414 issues
on Linux/arm (+178% more detected issues compared to allyesconfig).
For Linux/x86, 145 (+57.6%) issues can be expected. These projections indicate that a more thorough engineering in the variability extractors, such
as improving the KCONFIG variability extractor (cf. Section 3.1) or further
improvements to the build system extractor GOLEM (cf. Section 3.2), promise
to make the approach even more effective to improve the quality of system
software with existing tools.
4.2.6.2. Higher Coverage Criteria
The goal of this work is to ensure that each line of code is subject to compilation
– which is not ensured by the state-of-the-art tools and the (Linux) build process.
The overhead of additional compilation runs, which result from analyzing the
produced configurations individually, is hereby kept to an acceptable level of
10 to 30 percent in Linux. However, there still might be a number of missed
issues that GCC would have been able to detect, if the correct configuration
102
4.2. Configuration Coverage
had been selected.
Higher coverage criteria, such as decision coverage or path coverage are likely
to reveal even more issues. However, given that the complexity of the problem
to reach statement coverage already has a complexity of NP-hard, algorithms
for higher coverage criteria might require much longer run times and much
more configurations than would be acceptable in a tool that developers use in
their regular development cycle. Investigating such algorithms (and efficient
approximations), remains an area of future research.
4.2.6.3. Static Scanner Selection
The presented approach aims at making existing tools for static analysis configurability aware. During the development of VAMPYR, I have also experimented
with the static checker SPARSE. On the standard configuration allyesconfig,
SPARSE already reports on Linux/arm 9,484, and on Linux/x86 12,798 errors
and warnings. With VAMPYR, SPARSE reveals 23,964 issues on Linux/arm
(+60.4% not in allyesconfig) and 14,561 issues on Linux/x86 (+12.1%
not in allyesconfig). I have not analyzed the SPARSE results in detail –
the sheer volume of messages even with allyesconfig indicates that it is
not really used by Linux developers. Nevertheless, the application of VAMPYR
practically doubles the issues identified by SPARSE, which is an effect of the
greatly improved CC.
C OCCINELLE [Pal+11] is another tool that VAMPYR is able to drive. A major
difference to other static scanners is that C OCCINELLE does not work on
a particular configuration, but is somewhat configurability aware: Where
possible, it internally converts #ifdef to if() statements for the analysis. The
limitations of this approach are CPP blocks with #else branches that cannot
be translated to run-time evaluated branches with the if() statements, such as
typedef statements in headers, or outside of function implementations. Such
situations occur particularly often in the HAL implementation of Linux. In
these cases, C OCCINELLE uses the first variant that does not lead to a parsing
error and misses the alternative. I have therefore devised a preprocessor
that effectively disables C OCCINELLE’s #ifdef heuristics and uses the VAMPYR
tool, instead making C OCCINELLE fully configurability aware. On Linux/arm
and Linux/x86 this results in 8 additional messages (an increase by 4.3%).
This indicates that also static scanners that are not completely configurabilityagnostic can profit from the configuration-aware scanning approach.
In this thesis I focus on the analysis with GCC, because it represents an
accepted static checker that can be considered as the minimal common denominator: Every programmer is expected to deliver code that does at least
compile. Given that each new version of GCC introduces additional, and increasingly more accurate warnings, and error messages, the integration of
103
Chapter 4. Case Studies
variability awareness in form of the compilation driver
very effective and practical tool.
VAMPYR
results in a
4.2.6.4. General Applicability of the Approach
The VAMPYR approach results in an implementation to find additional bugs
in operating systems and system software that are hidden in conditionally
compiled code. The approach can also be implemented for other software
families as well, given there is some way to identify variation points and to
extract the corresponding constraints from all sources of variability. This is
straightforward for the implementation space (code and MAKE files) in most
cases, which is generally configured by CPP or some similar preprocessor.
Extracting the variability from projects that do not use KCONFIG is straightforward as well, as long as features and constraints are described by some
formal model. The configurability of eCos, an operating system for embedded
systems for instance, is described in the CDL, for which its expressiveness and
dependency declarations have been compared to KCONFIG before in the literature [Ber+10c]. I expect that the variability extractors used in that work can
be integrated into the VAMPYR tool to help the eCos developers with finding
bugs in conditionally compiled code.
4.2.7. Summary: Configuration Coverage
System software typically can be configured at compile-time to tailor it with
respect to the supported application or hardware platform. The Linux kernel,
for instance, provides more than 12,000 configuration options in version 3.2,
nearly 90 percent of which are related to hardware support. This enormous
configurability imposes great challenges for software testing with respect to
configuration coverage.
Existing tools for static analyses are configurability-agnostic: Programmers
have to manually derive concrete configurations to ensure configuration coverage. Thereby, many easy-to-find bugs are missed, just because they happen to
not be revealed by a standard configuration – Linux contains quite some code
that does not even compile. With the VAMPYR approach and implementation,
the necessary configurations can be derived automatically. VAMPYR is easy
to integrate into existing tool chains and provides configurability-awareness
for arbitrary static checkers. With GCC as a static checker, I have revealed
hundreds of issues in Linux/arm, Linux/x86, F IASCO, and Linux/arm. For
Linux/arm, I have found 52 new bugs, some of which went unnoticed for
six years; the submitted patches for Linux and F IASCO where accepted or
confirmed. I have found these new bugs by increasing the CC from 59.9% to
84.4%, and they are yet only the tip of the iceberg.
104
4.3. Configuration Tailoring
4.3. Configuration Tailoring
The Linux kernel is a commonly attacked target. Alone in 2011, 148 issues
for Linux have been recorded in the Common Vulnerabilities and Exposures
(CVE) [Cor99] security database, and this number is expected to grow every
year.
This is a serious problem for system administrators who rely on a distributionmaintained kernel for the daily operation of their systems. On the Linux
distributor side, kernel maintainers can make only very few assumptions
on the kernel configuration for their users: Without a specific use case, the
only option is to enable every available configuration option to maximize the
functionality. On the user side, system administrators are mainly interested
in operating their particular machines, and have little tolerance to problems
introduced by functionality that they do not use. The ever-growing kernel code
size, caused by the addition of new features such as drivers and file systems,
at an increasing pace, indicates that the Linux kernel will be subject to more
and more vulnerabilities.
Moreover, experience from the development and patching process of large
software projects shows that the less a feature is used, the more likely its
implementation contains bugs. Indeed, developers mostly focus on fixing
issues that are reported by their customers. As rarely used functionalities
only account for reliability issues in a small portion of the user base, this
process greatly improves the overall reliability of the software. However,
malicious attackers can, and do, still target vulnerabilities in those less-oftenused functionalities. A recent, prominent, example is a vulnerability that
allows the read and write arbitrary kernel memory in the implementation of
reliable datagram sockets (RDS) (CVE-2010-3904), a rarely used socket type.
If the intended use of a system is known at kernel compilation time, an
effective approach to reduce the kernel’s attack surface is to configure the
kernel to not include unneeded functionality. However, finding a suitable
configuration requires extensive technical expertise about currently (i.e., Linux
v3.2) more than 12,000 configuration options, and needs to be repeated at
each kernel update. Therefore, maintaining such a custom-configured kernel
entails considerable maintenance and engineering costs.
A number of use-cases have clear requirements on the functionality that is
required from the Linux kernel. In order to reduce the potential attack surface
from kernel-targeted attacks, I propose to statically configure the Linux kernel
in a way that includes only those kernel options that are necessary to fulfill the
requirements. With the holistic variability model ϕ presented in Chapter 3 and
execution traces from a running system, it is possible to automatically produce
scenario-tailored Linux configurations, which can be loaded and compiled
with the standard Linux build infrastructure.
105
Chapter 4. Case Studies
1
2
_______
_______
_______
_______
enable
tracing
run workload
& store trace
3
Makefile
arch/x86/init.c:59
arch/x86/entry32.S:14
arch/x86/...
lib/Makefile
kernel/sched.c:723
...
correlate to
source line locations
B00 <-> CONFIG_X86
&&
B1 <-> CONFIG_NUMA
&&
B2 <-> ! B1
&&
...
establish a
propositional
formula
4
CONFIG_X86=y
CONFIG_NUMA=y
CONFIG_SCSI=m
...
...
5
derive a kernel
configuration
6
complete the
configuration
Figure 4.9.: Kernel-configuration tailoring workflow
4.3.1. Kernel-Configuration Tailoring
The goal of kernel-configuration tailoring is to improve the overall system
security of Linux as shipped by Linux distributions such as Ubuntu or Red Hat
Linux. These popular distributions are unwilling (or are unable) to ship and
maintain a large number of different kernels because of maintenance costs.
Therefore, their kernel package maintainers configure the distribution kernels
to be as generally usable as possible, which requires to select a Linux kernel
configuration that enables basically every configuration option available.
Unfortunately, this also maximizes the attack surface. As security-sensitive
systems do not require the provided genericness, the attack surface can be
reduced by simply not enabling unnecessary features. What features are
necessary, however, depends on the actual workload of the corresponding
use-case.
The approach for Configuration Tailoring first analyzes the workload at run
time. Then, a tool derives a reduced Linux configuration that enables only
the functionality based on the functionality that the administrator observes in
the analysis phase. This section shows the fundamental steps of the approach,
which is depicted in Figure 4.9.
Ê Enable tracing. The first step is to prepare the kernel to record which
parts of the kernel code are executed at run time. For this, I employ the Linuxprovided FTRACE feature, which is enabled with the KCONFIG configuration
option CONFIG_FTRACE.13 Enabling this configuration option modifies the Linux
build process to include profiling code that allows to record the executed
code. More precisely, the memory addresses of the executed code in the code
segment is stored in an internal buffer at run time. These internal buffers are
accessed in the next steps.
In addition, the approach requires a kernel that is built with debugging information so that any function addresses in the code segment can be correlated
to functions and thus, source file locations in the source code. For Linux, this
is configured with the KCONFIG configuration option CONFIG_DEBUG_INFO.
The Linux kernel that comes with the Ubuntu distribution ships with both
debugging information14 , and the FTRACE feature turned on. This indicates
13
14
Other tracing mechanisms would be thinkable as well.
Ubuntu provides separate packages that can be downloaded and installed separately.
106
4.3. Configuration Tailoring
that the Ubuntu kernel package maintainers, who are experienced with the
Linux configuration and build systems KCONFIG and K BUILD, are positive that
the performance overhead of FTRACE is negligible for all typical usages. For this
reason, my experiments use Ubuntu Linux as this allows using this distribution
kernel for tracing.
To also cover code that is executed at boot time by initialization scripts,
FTRACE needs to be enabled as early as possible. In Linux, this is best achieved
by modifying the initial ram-disk, which contains programs and LKMs for lowlevel system initialization.15 Linux distributions use initial ram-disks to detect
installed hardware early in the boot process and, mostly for performance
reasons, load only the required essential device drivers. Instrumenting the
initial ram-disk basically turns on tracing even before the first process (that is,
init) starts.
Ë Run workload. In this step, the system administrator runs the targeted
application or system services. The FTRACE feature now records all addresses
in the code segment for which code has been instrumented. This covers
most code in Linux but a small amount of operation-critical code such as
interrupt handling, context switches and the tracing feature itself, which are
not configurable and unconditionally compiled in every configuration.
To avoid overloading the system with often accessed kernel functions,
FTRACE allows adding code locations to an ignore list that excludes code
from being traced. The configuration tailoring approach fills this list dynamically to avoid functions to appear more often than once in the resulting
analysis report.
During this run, the list of observed kernel functions gets saved in regular
intervals. This allows determining what functionality was accessed at what
time, and thus, monitoring the evolution of the tailored kernel configuration
over time based on these snapshots.
Ì Correlation to source lines. A system service translates the raw address
offsets into source line locations using the ADDR 2 LINE tool from the binutils
tool suite. Because LKMs are relocated in memory depending on their (nondeterministic) order of loading, the system service compares the traced raw
addresses to offsets in the LKM’s code segment. This allows the detection
of functionality that is not compiled statically into the Linux kernel. The
correlation of absolute addresses in the code segment with the debug symbols
allows identifying the source files and the #ifdef blocks that are actually being
executed during the tracing phase.
Í Establishment of the propositional formula. Next, UNDERTAKER -TAILOR,
an extension of the UNDERTAKER tool presented in Section 4.1 and Section 4.2,
15
This part of the Linux plumbing is often referred to as “early userspace”: The initial ram-disks has
as primary task to identify the device and location of the target system. After having mounted the
root filesystem, it frees up the occupied system resources and hands control over to the actual init
process with PID 1, which continues the regular system startup. [Ear].
107
Chapter 4. Case Studies
translates the source-file locations into a propositional formula, using the extractors presented in Chapter 3 for ϕKCONFIG , ϕK BUILD and ϕCPP . The propositional
variables of this formula are the variation points and follow the conventions
introduced in Chapter 3.
Î Derivation of a tailored kernel configuration. With the established
formula, the UNDERTAKER tool proves the satisfiability of this formula. As a
side product, PICOSAT returns a concrete configuration that fulfills all these
constraints. Note that finding an optimal solution to this problem has a
computational complexity of NP-hard and is not the focus of this work. Instead,
the UNDERTAKER tool relies on heuristics and configurable search strategies in
the SAT checker to obtain a sufficiently small configuration.
As the resulting kernel configuration will contain some additional unwanted
code, such as the tracing functionality itself, the UNDERTAKER -TAILOR tool
allows the user to add additional constraints to the formula. This can be used
to force the selection (or deselection) of certain KCONFIG features, which can
be specified in whitelists and blacklists. This results in additional constraints
being conjugated to the formula just before invoking the SAT checker.
Ï Completing the Linux kernel configuration. The resulting kernel configuration now contains all features that have been observed in the analysis
phase. The caveat is that the resulting propositional formula can only cover
KCONFIG features of code that has been traced in step Ë. Therefore, this
formula specifies a partial configuration (cf. Section 4.2.3). The general hypothesis is that unreferenced features are not necessary and can therefore be
safely deselected.
However, the dependency constraints in ϕ make finding the best configuration non-trivial. The problem of finding a feature selection with the smallest
number of enabled features, (which is generally not unique) has the complexity
NP-hard. I therefore rely on heuristics to find a sufficiently small configuration that satisfies all constraints of KCONFIG, but is still significantly smaller
compared to a generic distribution kernel. Technically, my prototype implementation again employs the Linux KCONFIG tool to expand the configuration
with the allnoconfig strategy as explained in Section 4.2.3.
4.3.2. Evaluation
I evaluate the automatic tracing approach with two use cases. The first one is
a LAMP-based server that serves both, static web pages, as well typical web
applications such as a wiki and forum software. The second use case is a
graphical workstation that additionally shares part of the local hard drive via
the network file system (NFS) service.16 Both applications run on distinct,
16
This is the system configuration of the workstations in the student lab in the computer science
laboratory of my chair at the University of Erlangen at the time of writing.
108
4.3. Configuration Tailoring
non-virtualized hardware. This evaluation demonstrates the approach with
practical examples, and verifies that the obtained kernel is functional, that is,
no required configuration option is missing in the tailored kernel. Moreover,
this section shows that the performance of the kernel with the generated
configuration remains comparable to that of the distribution kernel.
Both machines use the 3.2.0-26 Linux kernel distributed by Ubuntu
12.04.1 as baseline. To compare the performance in a meaningful way, I
use benchmarks that are specific to the use case. The benchmarks compare
the original, distribution-provided kernel to the generated, tailored kernel.
All requests are initiated from a separate machine over a gigabit Ethernet
connection. To avoid interferences by start-up and caching effects right after
the system boots, the workload and measurements start after a warm-up phase
of 5 minutes.
4.3.2.1. LAMP-stack use case
The first use case employs a machine with a 2.8 GHz Celeron CPU and 1 GB of
RAM, and runs the Ubuntu 12.04 server edition with all current updates and
no modifications to neither the kernel, nor any of the installed packages. As
described in Section 4.3.1, the system-provided initial RAM disk (initrd) is
instrumented to enable tracing very early in the boot process. In addition, the
system is configured to provide a web platform consisting of A PACHE 2, M Y SQL
and PHP. The system serves static documents, the collaboration platform
D OKU W IKI [Goh] and the message board system PHP BB3 [Php] to simulate a
realistic use case.
The test workload for this use case starts with a simple HTTP request using
the tool WGET, which fetches a file from the server right after the five-minute
warm-up phase. This is followed by one run of the HTTPERF [MJ98] tool, which
accesses a static website continuously, increasing the number of requests per
second for every run. Finally, the S KIPFISH [ZHR] security tool scans the server
to execute as many code paths as possible.
Figure 4.10 depicts the number of KCONFIG features that the tool obtains
from the trace logs (on the y-axis) collected at the times given on the x-axis.
After the warm-up phase, connecting to the server via ssh causes a first
increase in observed unique KCONFIG features. The simple HTTP request
triggers only a small further increase, and the number of unique configuration
options converges quickly after the HTTPERF tool is run. There are no further
changes when proceeding with the S KIPFISH scan. This shows that for the
LAMP use case, a tracing phase of about five minutes is sufficient to detect all
required features.
The trace file upon which the kernel configuration is generated, is taken
1,000 seconds after boot, that is, after running the tool HTTPERF, but before
109
Chapter 4. Case Studies
500
enabled KConfig features
495
skipfish
490
485
wget
480
httperf
475
ssh
470
465
0
300
600
900
1200
1500
1800
time in s after finished boot (in runlevel 3)
2100
Figure 4.10.: Evolution of KCONFIG features enabled over time. The bullets mark the
point in time at which a specific workload was started.
Standard 3.2.0-26-generic #41_Ubuntu SMP Kernel
Webserver
1090
447
5
static 1537
452
modules 3168
source
8670
files
3125
17 26
43
1121
7549
1121
0
Figure 4.11.: Comparison of features and compiled source files between the kernel
configuration shipped with Ubuntu 12.04, and the tailored configuration
for LAMP server use (results for the workstation with NFS use case are
similar).
the S KIPFISH tool. It consists of 8,320 unique function addresses, including
195 addresses from LKMs. This correlates to 7,871 different source lines in 536
files. The prototype generates the corresponding configuration in 145 seconds
and compiles the kernel in 89 seconds on a commodity quad-core machine
with 8 GB of RAM.
When comparing the original kernel to the distribution kernel shipped
with Ubuntu, there is a reduction of KCONFIG features that are statically
compiled into the kernel of over 70%, and almost 99% for features that lead to
compilation as LKMs (cf. Table 4.8). Consequently, the overall size of the code
segment for the tailored kernel is over 90% lower than that of the baseline
kernel supplied by the distribution.
Figure 4.12 relates the number of source code files that the tailored configuration does not include when compared to the distribution configuration.
The reduction of the Trusted Computing Base (TCB) is significant. The figure
breaks down the reduction of functionality by subdirectories in terms of source
110
4.3. Configuration Tailoring
0
arch
block
crypto
1000
6000
95%
86%
38%
34%
lib
25%
removed files from tailored
kernel compared to Ubuntu
standard
8%
87%
net
others
5000
71%
kernel
sound
4000
15%
fs
mm
3000
33%
drivers
ipc
2000
100%
source files in both kernels
62%
Figure 4.12.: Reduction in compiled source files for the tailored kernel, compared
with the baseline in the LAMP use case (results for the workstation with
NFS use case are similar). For every subdirectory in the Linux tree, the
number of source files compiled in the tailored kernel is depicted in
blue and the remainder to the number in the baseline kernel in red. The
reduction percentage per subdirectory is also shown.
files that get compiled. The highest reduction rates are observed inside the
sound/ (100%), drivers/ (95%), and net/ (87%) directories. As the web
server does not play any sounds, the trace file does not indicate any soundrelated code. Similarly, the majority of drivers are not needed for a particular
hardware setup. The same applies to most of the network protocols available
in Linux, which are not required for this use case. Out of 8,670 source files
compiled in the standard Ubuntu distribution kernel, the tailored kernel only
required 1,121, which results in an overall reduction of 87% (cf. Table 4.8).
A critical requirement for the approach is the stable operation of the resulting
kernel. In order to test this systematically, the S KIPFISH [ZHR] tool suite is
run on both the baseline kernel and the tailored kernel. Skipfish is a tool for
performing automated security checks on web applications, hence exercising a
number of edge-cases, which is valuable for testing the stability of the tailored
use case. The report produced by the tool finds no significant difference from
one kernel configuration to the other. This indicates that the tailored kernel
can handle unusual web requests not covered during the tracing phase equally
well and thus, that the tailored kernel is suitable for the stable operation of
the service.
Another critical feature is performance. In this evaluation, I employ the
benchmark utility HTTPERF [MJ98], a standard tool for measuring the web-
111
Chapter 4. Case Studies
54
52
replies/s
50
48
46
44
baseline kernel
42
tailored kernel
40
0
100
200
300
400
500
600
700
request rate in req/s
Figure 4.13.: Analysis of reply rates of the LAMP-based server using the kernel shipped
with Ubuntu and the tailored kernel. Confidence intervals were omitted,
as they were too small and would impair the readability.
server performance. When comparing the results to a run performed on the
same system that runs the baseline kernel as depicted in Figure 4.13, it turns
out that the tailored kernel achieves a performance that is very similar to that
of the kernel provided by the distribution.
4.3.2.2. Workstation/NFS use case
I evaluate the workstation/NFS server use case on a 3.4 GHz quad-core machine with 8 GB of RAM that runs the Ubuntu 12.04 Desktop edition, again
without modifications to packages or kernel configuration. The machine is
configured to export a local directory via NFS.
To measure the performance of the different kernel versions, I employ the
Bonnie++ [Cok] benchmark, which covers reading and writing to this directory
over the network. The experiment is conducted without caching on both server
and client.
The trace file of the configuration selected for further testing consists of
13,841 lines that reference a total of 3,477 addresses in modules. This resolves
to 13,000 distinct source lines in 735 files. Building the formula for constructing the Linux configuration takes 219 seconds, compiling the kernel another
99 seconds on the same machine as described above. The number of KCONFIG
features that are statically compiled into the kernel reduces by 68%, 98% for
features compiled into LKMs, and by about 90% less code in the code segment.
There is no noticeable impact on the regular functionality of the workstation.
All hardware attached, such as input devices, Ethernet or sound, remain fully
operable when booting the tailored kernel. Using the tailored kernel, the
Bonnie++ runs again with the same parameters to compare the results with
112
4.3. Configuration Tailoring
LAMP Use-Case
Kernel (vmlinux) size in Bytes
LKM total size in Bytes
Options set to ’y’
Options set to ’m’
Compiled source files
Baseline
Tailored
Reduction
9,933,860
62,987,539
1,537
3,142
8,670
4,228,235
2,139,642
452
43
1,121
56%
97%
71%
99%
87%
Baseline
Tailored
Reduction
9,933,860
62,987,539
1,537
3,142
8,670
4,792,508
2,648,034
492
63
1,423
52%
96%
68%
98%
84%
Workstation/NFS Use-Case
Kernel (vmlinux) size in Bytes
LKM total size in Bytes
Options set to ’y’
Options set to ’m’
Compiled source files
Table 4.8.: Results of the kernel-configuration tailoring
those of the distribution kernel. Figure 4.14 shows that also in this use case,
the kernel compiled with the tailored configuration achieves a very similar
performance.
4.3.3. Discussion
In this section, I discuss both the key strengths and weaknesses of the automated kernel configuration tailoring approach.
As Figure 4.12 demonstrates, a significant portion of code no longer gets
compiled and thus, reduces the attack surface by almost an order of magnitude.
As such, vulnerabilities existing in the Linux kernel sources are significantly
less likely to impact users of a tailored kernel. This makes the approach an
effective means for improving security in various use cases.
The approach presented relies on the assumption that the use case of the
system is clearly defined. With this a-priori knowledge, it is possible to
determine which kernel functionalities the application requires and therefore,
which kernel configuration options have to be enabled. With the increasing
importance of compute clouds, where customers use virtual machines for very
dedicated services, the approach is promising for improving the security in
many cloud deployments.
Most of the steps presented in Section 4.3.1 require no domain-specific
knowledge of Linux internals. I therefore expect that they can be conducted in
a straightforward manner by system administrators without specific experience
in Linux kernel development. The system administrator, however, continues
to use a code base that constantly receives maintenance in the form of bug
113
Chapter 4. Case Studies
block read
block rewrite
block write
tailored kernel
throughput
in MB/s
0
20
40
60
baseline kernel
80
100
120
Figure 4.14.: The analysis of the test results from the Bonnie++ benchmark shows no
significant difference between the tailored and the baseline kernel.
fixes and security updates from the Linux distributor. I am therefore confident
that my approach makes tailoring a kernel configuration for specific use-cases
automatically both practical and feasible to implement in real-world scenarios.
Both experiments show that, for proper operation, the resulting kernel
requires eight additional KCONFIG options, which the ftrace feature could
not detect. The whitelist mechanism allows specifying wanted or unwanted
KCONFIG options independently of the tracing. This provides an interface for
further methods and tools to assist the approach with hints, which help to
determine kernel features that tracers such as FTRACE cannot observe.
Previous approaches that reduce the Linux kernel’s TCB, such as the SEC COMP system-call filtering by Google [Goo09] or KTRIM by Kurmus, Sorniotti,
and Kapitza [KSK11], introduce additional security infrastructure in form of
code that prevents functionality in the kernel from being executed, which
can lead to unexpected impacts and the introduction of new defects into the
kernel. In contrast to those works, configuration tailoring modifies the kernel
configuration instead of changing the kernel sources (e.g., [Lee+04; Spe]) or
modifying the build process (e.g., [Cri+07]). In that sense, only creating new
kernel configurations cannot introduce new defects into the kernel by design.
However, as the configurations produced are specific to the use case analyzed
in the tracing phase, it cannot be ruled out that the tailored configuration
uncovers bugs that could not be observed in the distribution-provided Linux
kernel. Although such bugs have not been encountered in the experiments,
I would expect them to be rather easy to fix, and of rare occurrence, as
the kernels produced contain a strict subset of functionality. In some ways,
114
4.3. Configuration Tailoring
configuration tailoring could therefore even help improve Linux by uncovering
bugs that are hard to detect.
This also emphasizes the importance of the analysis phase, which must be
sufficiently long to observe all necessary functionality. In case of a crash or
similar failure, however, one could only attribute this to a bug in either the
kernel or the application implementation that needs to be fixed. In other
words, the approach is safe by design.
4.3.4. Summary: Configuration Tailoring
Linux distributions ship generic kernels, which contain a considerable amount
of functionality that is provided just in case. For instance, a defect in an
unnecessarily provided (and unnecessarily or forcefully loaded) driver may be
sufficient for attackers to take advantage of. The genericness of distribution
kernels, however, is unnecessary for concrete use cases. Configuration Tailoring
optimizes the configuration of the Linux kernel and results in a hardened
system that is tailored to a given use case in an automated manner.
I demonstrate the usefulness of my prototype implementation in two scenarios, a LAMP stack and a graphical workstation that serves data via NFS.
The resulting configuration leads to a Linux kernel in which unnecessary functionality is removed at compile-time and thus, inaccessible to attackers. The
approach improves the overall system security and is practical for most use
cases because of its applicability, effectiveness, ease, and safety of use.
115
Chapter 4. Case Studies
4.4. Chapter Summary
The non-homogeneous development of the configuration space and implementation space causes inconsistencies, many of which manifest in actual bugs
in today’s system software. Linux is no exemption from this. In fact, Linux
is an excellent subject for demonstrating the various manifestations of issues that a highly-configurable piece of (system) software causes during both
development and maintenance.
Many of these issues can be addressed with static analysis and tool support.
For this reason, I have developed a suite of tools around the development
prototype UNDERTAKER, which I have made freely available for both practitioners and researchers. The UNDERTAKER tool uses the models ϕConfiguration
and ϕImplementation as described in Chapter 3 to reveal thousands of dead and
undead blocks in Linux and related system software, which is described in
detail in Section 4.1. These insights lead the definition of the concept of
Configuration Coverage, which allows developers to significantly expand the
analysis coverage of existing tools for static analysis on legacy software in
Section 4.2. Lastly, in Section 4.3 I show how UNDERTAKER can also assist system integrators with the weary task of finding adequate system configurations
based on experimental analysis of real systems. In summary, UNDERTAKER is
a versatile tool for mastering many kinds of variability challenges in system
software.
116
5
Summary and Conclusion
Kconfig isn’t a very sophisticated language it’s the COBOL of config languages.
(Ingo Molmar, April 2013 on the LKML)
Configurability in system software imposes many engineering challenges
that give developers, subsystem maintainers, and system integrators a hard
time to handle correctly. In Chapter 2, I have identified the root cause lies
in the non-homogeneous management of variability. This is the current state
of the art and therefore commonly found in today’s system software, which
includes the popular and economically relevant operating system Linux, and
as shown in this thesis, this architecture can be observed in many other system
software projects as well.
5.1. Achieved Accomplishments
The ultimate vision that inspired me to work on this subject was to achieve
mastery over variability challenges in Linux and related highly-configurable
system software. I measure my accomplishments towards that goal with the
questions that I have posed in Section 1.2.
Answer to Question 1: Today’s configuration mechanisms do cause types of
bugs that require special attention.
As shown in Section 4.1, Linux inhibits thousands of configurability defects,
which are hard to detect and have a very different severity. As my evaluation
shows, Linux developers are happy to accept patches that solve those issues.
117
Chapter 5. Summary and Conclusion
The problem is not limited to Linux. I could successfully show that also other
systems, including F IASCO, B USY B OX, C OREBOOT and many others suffer from
similar symptoms. In summary, configuration consistency is both a common
and widespread problem.
It turns out that for projects that implement variability with the CPP, configurability issues manifest in variation points that can never be enabled (so
called dead #ifdef blocks), and blocks that are present in all configurations
(so called undead #ifdef blocks). Both kinds of defects arise from the logical
implications that stem from the variability declaration and implementation.
The solution that I propose, is to formalize both in form of propositional formulas, and check the presence condition for each variability point (i.e., #ifdef
block and conditionally compiled source file) for contradictions or tautologies.
This approach reveals over three thousand of such defects in recent Linux
versions.
Answer to Question 2: Static configurability does significantly hinder the
systematic application of tools for static analysis.
The reason for this is that the static analysis requires a preprocessed, and therefore fully configured, source tree for many analyses such as type checking, et
cetera. By analyzing the Configuration Coverage of Linux on various architectures, it turns out that the typical configurations, such as allyesconfig (which is
actually supposed to cover as much code as possible), achieves a surprisingly
poor coverage on many architectures (cf. to Section 4.2.2 for details). The
reason for this observation is simple: A single configuration cannot be enough
to cover alternative variation points, for instance as implemented by #else
blocks.
To address this issue, I propose to calculate a reasonably small set of configurations that in total maximize the Configuration Coverage. Using this approach,
I am able to increase the CCN metric on Linux/x86 by 13% and on Linux/arm
by 24%. This allows kernel developers to reveal many hard to detect issues
that are buried by complicated KCONFIG constraints and #ifdef expressions,
which can now be systematically tested by common tools such as GCC.
Answer to Question 3: Run-time analysis can greatly assist with finding a
sufficient kernel configuration that minimizes the attack surface and thus,
makes systems more secure.
The more than 5,000 user-configurable features of recent versions of Linux/x86 alone make configuring Linux for given use-cases a challenging task.
Therefore, using the distribution provided Linux kernel is a safe choice, especially for system administrators that are less experienced with the Linux
configuration system and the provided features. The thereby employed kernel
configurations have to be generic, which basically means that kernel maintain-
118
5.2. Contributions
ers of Linux distributions (have to) enable as many features (and therefore
as many configuration options) as possible, to maximize the suitability for as
many deployments as possible. This, however, also maximizes the Trusted
Computing Base (TCB) of the running kernels, and imposes avoidable risks to
users.
One approach to address this problem is to tailor the Linux kernel configuration to include only the actually required functionality. This is hard because of
the complex constraints that the configuration systems KCONFIG and K BUILD
impose; even for Linux developers it is often difficult to understand how to
configure a given feature for a specific use-case. In this thesis, I address
this challenge with run time analysis, and observe the actually required functionality in test runs. Based on run-time traces, I generate tailored kernel
configurations.
5.2. Contributions
Despite being the state of the art, non-homogeneous variability management
imposes a number of non-obvious challenges for developers, system integrators
and users. I am addressing the resulting challenges by identifying the relevant
sources of variability, extract them individually, and combine the result to
a holistic variability model ϕ (cf. Chapter 3). This is instrumental for the
applications presented in Chapter 4 for helping developers to automatically
find inconsistencies, allowing testers to ensure an adequate test coverage,
and assisting system integrators with the tedious task to find a Linux kernel
configuration that is optimal for their particular use-case.
In simple words, the approach and tools that I present in this thesis analyze
the configuration variability that is maintained separately from the implementation. This is typical for large scale software systems like system software.
The biggest challenge to get the results accurate lies in the accurate extraction
of the dependency information from all source artifacts that control variability.
While this is challenging engineering wise, in Chapter 3 I demonstrate the
feasibility. The results speak for themselves: Over hundred (accepted) patches
(cf. Section 4.1) show that configuration inconsistencies are a significant problem that today’s Linux developers do want to see fixed, and the over three
thousand defects in Linux v3.2 indicate that so far only the tip of the iceberg
has been addressed.
In Section 4.2, I extend my approach to make current tools for static analysis
cover more code, which is impossible to achieve with a single configuration.
In some ways, the VAMPYR approach to maximize the CC can be seen as a
simplified form of combinatorial testing [Coh+96] that operates on KCONFIG
options, with the optimization of only considering the CPP items that are
actually mentioned in #ifdef expressions in the analyzed source file. It
119
Chapter 5. Summary and Conclusion
would be interesting to investigate in what ways the research on pairwise
testing [OMR10] can be integrated in form of additional coverage algorithms
in order to make the VAMPYR tool reveal even more bugs with existing static
checkers. Nevertheless, even in the current implementation VAMPYR is an
easy-to-use asset for every Linux developer and subsystem maintainer.
The holistic variability model ϕ also has additional uses. In Section 4.3, I
combine the extracted constraints with run-time traces to tailor a use-case–
specific kernel configuration. The result is a toolchain that operates almost
unattendedly after the tracing phase, and can help to significantly improve
the system exposure to malicious attacks. The results from the evaluation are
promising: In typical use-cases, the tailored configuration reduces the code
sizes by more than 95%.
5.3. Going Further and Future Work
The high relevance of static configurability for system software gives rise to the
question whether system programmers need better programming languages.
Ideally, the language and compiler would directly support configurability
(implementation and configuration), so that symbolic and semantic integrity
issues can be prevented upfront by means of type-systems, or at least be
checked for at compile-time.
While I am convinced that this would be a step in the right direction that,
however, I acknowledge that this also demands significant effort for developing
configuration-aware tools. The approach that I present in this thesis makes this
variability awareness available to legacy software without extra engineering
by improving the effectiveness of existing tools.
With respect to implementation of configurability, it is generally accepted
that the CPP might not be the right tool for the job [LKA11; SC92]. Many
approaches have been suggested for a better separation of concerns in configurable (system) software, including, but not limited to: object-orientation
[Cam+93], component models [Fas+02; Rei+00], aspect-oriented programming (AOP) [CK03; Loh+09], or Feature-Oriented Programming (FOP) [Bat04].
However, in the systems community we tend to be reluctant to adopt new
programming paradigms, mostly because we fear unacceptable run-time overheads and immature tools. C++ was ruled out of the Linux kernel for exactly
these reasons. While this is of course debatable, we have to accept CPP
as the de facto standard for implementing static configurability in system
software [Spi08; Lie+10].
With respect to modeling configurability, feature modeling and other approaches from the product line engineering domain [CE00; PBL05] provide
languages and tooling to describe the variability of software systems, including
systematic consistency checks. KCONFIG for Linux or CDL for eCos fit in here.
120
5.3. Going Further and Future Work
However, what is generally missing is the bridge between the modeled and the
implemented configurability. Hence tools like UNDERTAKER remain necessary.
When accepting the fact that today’s software inhibits a great amount nonhomogeneous variability implementations, my conclusion is that this calls for
tool support. The UNDERTAKER and VAMPYR tools are an important first step
for that. As the hierarchy of variability shown in Figure 2.3 in Section 2.1.2
on Page 13 indicates, a number of variability levels remain unaddressed. I
am convinced that my contributions can be extended to variability points that
are expressed in high-level programming languages such as C (e.g., by simply
if() branches), linker scripts, or even loadable kernel modules.
I have made all tools, with accompanying documentation and full sourcecode, freely available for both, researchers and practitioners, with the expectation that they will find them useful in their daily work. As the evaluations
on non-Linux systems show, the necessary adaptations are straightforward to
implement. By this, I hope to have provided a catalyst for future research on
variability in highly-configurable system software.
121
A
Translating K CONFIG into Propositional Logic
This appendix supplements Section 3.1.2 and provides additional details on
translation of variability expressed in KCONFIG into propositional logic.
A.1. Basic Types
Basic types are straightforward to translate. The extractor first parses all
KCONFIG entries in the source code and processes each item individually.
For KCONFIG features of type int, string, hex, and boolean the translation
process assigns a single, Boolean configuration variable that represents the
user selection of the feature.
Linux developers may choose to declare a feature as tristate. Unlike
boolean features, which can either be enabled or disabled, tristate features
can have one of three states: enabled, disabled, or compiled as kernel module.
The choice influences the binding time of the feature; tristate features that
are configured to hold the value ‘m’ can be loaded at run-time.
Technically for tristate features, the variability extractor introduces two
configuration variables, one of which by convention ends with the suffix
_MODULE. This convention allows translating the configuration variables directly
for the CPP and makefiles. The translation process inserts logical constraints
that prevent that both configuration variables can be enabled at the same time.
For instance, consider the KCONFIG feature declarations related to microcode
loading in Linux:
This excerpt defines a tristate feature without additional dependency constraints on other features. The help text, commonly presented in the graphical
123
Appendix A. Translating K CONFIG into Propositional Logic
config MICROCODE
tristate "microcode support"
select FW_LOADER
---help--If you say Y here, you will be able to update the microcode.
[...]
To compile this driver as a module, choose M here.
or textual configuration front-end, is advisory to the user and has no further effects on the declared variability constraints. The statement select FW_LOADER
means that if the user selects the feature MICROCODE, KCONFIG will automatically enable the feature FW_CONFIG.
From this feature declaration, the translation process produces the following
implications:
MICROCODE
MICROCODE_MODULE
MICROCODE
MICROCODE_MODULE
→
→
¬MICROCODE_MODULE
¬MICROCODE ∧ MODULES
→ FW_LOADER
→ FW_LOADER
The option MODULES is special as it handles the additional constraints for
handling loadable kernel module (LKM) support. If configured to the value
true, then the kernel supports LKMs. This constraint is not explicitly modeled
in the KCONFIG configuration files, but is hard wired into the KCONFIG tool
implementation.
KCONFIG items with a type of string, hex, and integer are treated similar
to boolean.
A.2. Choices
The KCONFIG language provides the choice construct to allow grouping of
features and support one of two cardinality constraint types: Boolean choices
allow exactly one of the grouped features to be selected. The other cardinality
constraint type, a tristate choice, allows the selection of more than one (in fact,
even all) of the grouped (tristate) features as a module. For both variants,
the keyword optional allows a selection that enables none of the grouped
features.
The translation process models such feature groups as regular features.
Since choice constructs do not have explicit feature names, the translation
124
A.3. Selects
process assigns the corresponding configuration variable a name that contains
a sequential number so that different choices are distinguishable. In my
prototype extractor, this leads to symbols, such as CHOICE_42 for the fortysecond choice item in KCONFIG.
To illustrate, consider the following KCONFIG fragment that allows users to
choose between two config items:
choice " High Memory Support "
config HIGHMEM4G [...] # These are 2
config HIGHMEM64G [...] # alternatives
endchoice
This KCONFIG definition is a so called “Boolean choice”, which means that the
user may select exactly one of the two alternatives. This logical constraint
is expressed in propositional logic with the “exclusive or” (XOR) operator.
Therefore, the extractor constructs the following propositional implication:
CHOICE_0 → XOR(HIGHMEM4G, HIGHMEM64G)
Alternatively, the developer can declare a choice as optional:
choice " High Memory Support "
optional
config HIGHMEM4G [...] # These are 2
config HIGHMEM64G [...] # alternatives
endchoice
In this case, the cardinality constraints of the feature group is changed so
that no member of the group needs to be selected. Technically, my extractor
realizes this by introducing a free variable:
CHOICE_0 → XOR(HIGHMEM4G, HIGHMEM64G, _FREE_1_)
The variable _FREE_1_ is used to model the cardinality constraints and not
referenced by any other rule. Similar to the choice configuration variables, a
sequential number ensures global uniqueness.
A.3. Selects
The select statement in KCONFIG triggers the automatic selection of the given
KCONFIG item. Informally, this allows programmers to express that “if this
option is active in a configuration, then the referenced item must be as well”.
125
Appendix A. Translating K CONFIG into Propositional Logic
In Linux, this is often used by an idiom that sets architecture specific “traits”
in form of KCONFIG options that are not visible in the configuration tool at all.
Consider the following example:
config X86
select HAVE_ARCH_GDB
select GENERIC_PENDING_IRQ if SMP
This feature gets translated to the configuration variable X86 with the following
presence implications:
X86
X86
126
→ HAVE_ARCH_GDB
→ (SMP → GENERIC_PENDING_IRQ)
B
Practical Impact of Configuration
Inconsistencies on the development of the
Linux kernel
In this thesis, I have created an approach and a prototype implementation for
the static analysis of variability declaration and its actual implementation that
developers can use on a day-to-day basis for their work in the Linux kernel
and related system software. Hereby, the results have made sustainable contributions: I apply the UNDERTAKER tool to detect configuration inconsistencies
and programming errors in Linux in Section 4.1.3.
In the following, I explain the impact of the patch submissions in more
details, and report on my experiences with participating in the Linux development processes.
B.1. The Linux Development Process
In order to understand the impact of the patch submission that resulted from
the work with UNDERTAKER, the processes of the Linux development community need to be understood. In this section, I provide a short overview. A more
elaborate explanation has been published by the Linux Foundation [CKHM12].
Changes to the Linux kernel happen through a strict review process. A
change may be adding a new functionality as well as modifying, correcting, or
removing an existing one. Changes are first proposed in the form of a commit,
using the version control system GIT [Cha09], on focused mailing lists read
by the relevant Linux subsystem experts. These changes are then reviewed by
127
Appendix B. Practical Impact of Configuration Inconsistencies on the development of the Linux kernel
expert developers, and approved changes are then committed into the focused
subsystem repositories under their control.
At the beginning of each new Linux release cycle, referred to as the merge
window, subsystem developers ask Linus Torvalds, who maintains the Linux
master GIT repository, to integrate the batched changes from their subsystem
into his master repository. From this master repository, official Linux releases
are made for use by Linux distributors and end users.
Each GIT commit contains information such as the author and date of the
commit, a summary of the problem, a detailed description of the commit,
as well as the patch applied. The patch contains the textual change to the
modified files in the so called unified diff format. The modified files can be
documentation files, KCONFIG files, source code files, or makefiles.
Most of the patch submissions in the evaluation in Section 4.1.3 have been
sent as change proposals as outlined above. Most of those proposed changes
fix more than a single configuration defect. For instance, many dead blocks
are accompanied by an undead block in the #else branch following an #ifdef
block. A change that removes the #ifdef annotation will generally resolve
both configuration defects.
B.2. Validity and Evolution of Defects
The evaluation in Section 4.1.3 shows that experts can easily verify the correctness of the patch submissions: From about 57 of the 123 submitted patches
were accepted without further comments. For 87 patches there is a strong confirmation that the patch addresses a valid defect because they have received
comments by Linux maintainers that maintain a public branch on the Internet,
or are otherwise recognized in the Linux community. In overall, the evaluation
shows that the UNDERTAKER approach and implementation leads to sustainable
improvements to the Linux kernel.
In the course of conduction the evaluation, it sometimes happened that if
one of the submitted patches was not sent to the correct address (which in
many cases is not straightforward to determine), the patch was forwarded
to the responsible developer. However, in a number of cases it turns out that
the code in question is in fact unmaintained – here, UNDERTAKER helps to find
orphaned code. Code gets orphaned if the original maintainer, usually the
author, loses interest or otherwise disappears. Such code is problematic as it
is likely to miss refactorings and similar changes in Linux. Questions about
the respective piece of code, like the proposed patches, remain unanswered,
which can confuse, demotivate or at least slow down developers whose work
touches such unmaintained pieces of source code. Larger refactorings that
affect orphaned code become harder to integrate. One way to address the
problem of orphaned code is to properly document when a piece of source
128
B.3. Causes for Defects
New and Fixed Configuration Defects over Linux Releases
70
Introduced Defects
Fixed Defects
60
50
40
30
20
10
0
1
rc
6-
5
.3
.6
v2
1
rc
1
rc
5-
.3
.6
v2
4
.3
.6
v2
.3
1
rc
4-
3
.3
.6
v2
.6
v2
.3
1
rc
3-
2
.3
.6
v2
.6
v2
.3
1
rc
2-
.3
.6
.6
v2
v2
1
1
rc
1-
.3
.6
.3
.6
v2
v2
0
0-
.3
.6
.3
.6
v2
v2
Figure B.1.: Evolution of defect blocks over various Kernel versions. Most of the
submitted patches have been merged after the release of Linux version
2.6.35.
code does not have a proper maintainer. The patch submission found by the
UNDERTAKER approach and implementation helps to detect such pieces of code
in Linux, and has in fact resulted in a number of updates to the MAINTAINERS
file.
Figure B.1 depicts the impact on a timeline of Linux kernel releases. To
build this figure, I have applied the UNDERTAKER tool on previous kernel
versions and calculated the number of configurability defects that were fixed
and introduced with each release. Most of the patches entered the mainline
kernel tree during the merge window of version 2.6.36. Given that the patch
submissions have already made such a measurable impact, a consequent
application of the approach and implementation, ideally directly by developers
that work on new or existing code, could significantly reduce the problem of
configurability-related consistency issues in Linux.
B.3. Causes for Defects
As detailed in Section 4.1 on Page 67, there are three kinds of configuration
inconsistencies. The first kind are Code defects, which can be shown in the
code by just extracting ϕCPP alone. Trivial examples for such defects are
#if 0 blocks, which are commonly used for commenting out code during
129
Appendix B. Practical Impact of Configuration Inconsistencies on the development of the Linux kernel
development.1 In addition, nested #ifdef blocks with the same expression (or
the negated expression in #else blocks) are a common cause for code defects
in Linux.
Logic defects are often caused by copy and paste (which confirms a similar
observation in [Eng+01]). Apparently code is often copied together with an
enclosing #ifdef-#else block into a new context, where either the #ifdef or
the #else branch is always taken (i.e., undead) and the counterpart is dead.
The detection of logic defects requires both, the formula of the configuration
variability model ϕConfiguration (cf. Section 3.1), and the implementation variability model ϕImplementation (cf. Section 3.2 and Section 3.3). These represent
the most challenging kind of inconsistencies to detect and to understand. The
evaluation in Section 4.1.3 did not extensively discuss this kind of defects
because the used variability model was incomplete: At the time of conducting
the evaluation, the variability model lacked makefile constraints, which are
essential for the majority of logic defects. Therefore, the investigation of this
kind of defects requires more practical work with the UNDERTAKER tool.
The majority of patches that have been submitted (106 out of 123 patches)
in the context of the analysis in Section 4.1.3 address referential defects. When
analyzing the patches in more depth, there are two main observations:
Feature Names There are 6 patches which are changing the name of the
feature being used in the CPP code. Out of these patches, 9 were accepted.
Additionally, from the set of patches for which developers suggested a
different fix or for which they already had a fix for, there are 9 patches that
originally removed dead code because of missing features, but developers
suggested leaving the code in, and guarding it with a different feature
that is defined in KCONFIG. This indicates that this CPP code block is
not useless, but has been guarded by an undefined feature causing it
to be dead. In four of these cases, we could tell from the developer’s
comments and the name of the suggested feature that the undefined
features being replaced were caused by a misspelling or typo such as,
using CONFIG_CPU_S3C24XX instead of CONFIG_CPU_S3C244X (i.e., typo is
putting an X instead of the 4).
Incomplete Patches In the comments of one of the patches for which develop-
ers suggested using a different feature in the CPP condition, it seems that
the missing feature got renamed in KCONFIG, but developers forgot to
rename it in the code. The developer’s responses to three other accepted
patches removing dead code also suggest that the missing features were
retired in previous patches, but the code was not updated to reflect that.
1
Since this technique is rather frequent, UNDERTAKER has been adjusted to ignore such blocks to not
skew the amount of found inconsistencies with “uninteresting” defects.
130
B.4. Reasons for Rule Violations.
These observations indicate that incomplete patches may be a common
cause for variability anomalies.2
B.4. Reasons for Rule Violations.
15 patches were rejected by Linux maintainers. For all of these patches, the
respective maintainers confirmed the defects as valid (in one case even a bug!),
but nevertheless prefer to keep them in the code. Reasons for this (besides
carelessness) include:
Responsibility uncertainties. In a number of cases, the documentation points
to a developer that does not react to emails. This can be because the
email is no longer read, the developer is on vacation or acknowledges the
bug but is too busy to actually react to this email. This makes it hard for
both, the patch submitter as well as other Linux developers, to identify if
the source file is still actively maintained, or needs a new maintainer.
Documentation. Even though all changes to the Linux source code are kept
in the version control system (GIT [Cha09]), some maintainers have
expressed their preference to keep outdated or unsupported feature
implementations in the code3 in order to serve as a reference or template
(e.g., to ease the porting of driver code to a newer hardware platform).
Of course, one has to respect this preference, however, this approach
is quite confusing. A better way would be to refactor these conditional
block into proper code documentation.
Out-of-tree development. In a number of cases, there are configurability-related
items that are referenced from code in private development trees only.
Keeping these symbolic defects in the kernel seems to be considered
helpful for future code submission and review.
It is debatable if all of the above are good reasons or not. Nevertheless,
when proposing a software change to an open-source project, the maintainer’s
preferences always have priority. The whitelist approach provides a pragmatic
way to make such preferences explicit – so that they are no longer reported as
defects, but can be addressed later if desired.
2
I have analyzed 15 patches for which a different patch was suggested, or for which the original patch
was renaming the feature being used with more detail. By studying the history of each anomaly to
understand how it got introduced, it turns out that 8 patches fix anomalies that have existed since
the related code block has been introduced (i.e., code was anomalous since inception). On the other
hand, 7 patches fix anomalies caused by previous incomplete patches.
3
It seems that not every maintainer is familiar and comfortable with using the GIT tooling on a regular
basis.
131
C
Build-System Probing
In Section 3.2, I report on a pratical approach for extracting the variability implemented in the Linux build system K BUILD that employs controlled execution
of critical parts. This section elaborates on the implementation details of the
GOLEM development prototype that has been presented in in Section 3.2.5.5 on
Page 42. First, the operational primitives of the GOLEM prototype is presented
in form of detailed pseudo-code. Then, an exemplary execution illustrates the
operation of the approach on a constructed example.
C.1. Basic Algorithms
The function C OMMON B UILD P ROBING, which is depicted in Algorithm 1 on
page 134, is the starting point of the probing algorithm. It uses two global data
structures that are shared among all presented functions. POV_Stack (Line 1)
is the working stack of 4-tuples (Sbase , FILEbase , DIRbase , pov) that encapsulate
the work items for every iteration. The stack is filled up by the helper function
T EST P OV in Line 5 and is described further below in more detail.
The work items consist of a base selection Sbase , which activates the variability points (VPs) (i.e., the source files in a Linux tree) in FILEbase , and the points
of variability (POVs) (i.e., the subdirectories that contain VPs) in DIRbase . The
POV DIRbase acts as the current working item. The variable VP_PC (Line 2)
holds all found selections for a specific VP. These selections are the result of
the algorithm.
The base selection Sbase is tested first (Line 4); all POVs that are unconditionally activated are pushed onto the stack by T EST P OV for further processing.
133
Appendix C. Build-System Probing
Algorithm 1 The algorithm for probing build system variability
1: POV_Stack := new Stack()
. The working Stack
2: VP_PC := new Map(VP 7→ List of Selections)
3: function C OMMON B UILD P ROBING
4:
T EST P OV(S∅ , ∅, ∅)
. Test the empty selection
5:
while POV_Stack.size() != 0 do
6:
(Sbase , FILEbase , DIRbase , pov) := POV_Stack.pop()
7:
if not S HOULD V ISIT P OV(Sbase , pov) then
8:
continue
9:
end if
10:
for all Snew in G ENERATE S ELECTIONS(Sbase , pov) do
11:
T EST P OV(Snew , FILEbase , DIRbase )
12:
end for
13:
end while
14:
P RINTA LL S ELECTIONS(VP_PC)
15: end function
The working stack is processed until it is empty. As described, the stack consists
of 4-tuples (Line 6). First, the approach determines whether the current working item is a valid candidate for processing, otherwise it is skipped (Line 7).
Starting from the current POV, all selections that might enable additional VPs
or POVs are generated and tested (Line 10). Technically, the T EST P OV function
probes for such items, and pushes them as additional work items onto the
stack POV_Stack.
When the working stack is empty, the mapping from a VP to all activating
selections is printed. This is the step that combines selection and generates a
propositional formula representing the build system variability (Line 14).
The function S HOULD V ISIT P OV (called in Line 7) is an optimization to avoid
that the same POV is visited multiple times unnecessarily.
Algorithm 2 After pov was activated by Sbase , further selections are generated from
Sbase and EXPRESSION _ IN _ DIR. The A LL F ULLFILLING S ELECTIONS function appends all
assignments of expr, that evaluate to true, to the base selection Sbase
1: function G ENERATE S ELECTIONS (Sbase , pov)
2:
Selectionsnew := new List of Selections()
3:
for all expr in EXPRESSION _ IN _ DIR (pov) do
4:
for all Spartial in A LL F ULLFILLING S ELECTIONS(expr, Sbase ) do
5:
Selectionsnew .append(Sbase ∪ Spartial )
6:
end for
7:
end for
8:
return Selectionsnew
9: end function
134
C.1. Basic Algorithms
Algorithm 2 shows how the helper function of the algorithm that generates
new selections. This helper function assumes that given pov is activated by
Sbase . The next step is to determine all expressions that belong to this pov
(Line 3). The A LL F ULLFILLING S ELECTIONS function generates partial selections
that fulfill one of these expressions, but do not conflict with the base selection
Sbase . The partial selections are combined with the base selection (Line 5),
and a list of to-be-tested selections is returned. For example, if the pov was
activated by {A} and has only one expression B ∨ C, the following selections
are generated: {{A, B}, {A, C}, {A, B, C}}.
Algorithm 3 The T EST P OV function calls the list build-system primitive, collects all
the newly selected VPs and pushes the newly found POVs onto the working stack.
1: function T EST P OV(Snew , FILEbase , DIRbase )
2:
(FILEnew , DIRnew ) := list (Snew )
3:
for all vp in (FILEnew − FILEbase ) do
4:
VP_PC[vp].append(Snew )
. VP found under new selection
5:
end for
6:
. Push new POVs onto the Stack
7:
for all pov in (DIRnew − DIRbase ) do
8:
POV_Stack.push(new tuple(Snew , FILEnew , DIRnew , pov) )
9:
end for
10: end function
The approach tests newly generated selections in T EST P OV (cf. Algorithm 3)
by querying the build system for all items (both VPs and POVs) that are
activated by Snew (Line 2). For all VPs that are now activated, but were
not activated before, Snew is added to the list of activating selections for
this VP (Line 4). All POVs that are newly activated generate a new 4tuple work-item on the stack (Line 8). These items are processed in the
C OMMON B UILD P ROBING function again.
Performance Optimization for K BUILD
The GOLEM prototype features an important optimization that helps to speed
up the variability extraction: The empty selection, which does not enable any
feature, is actually not a valid configuration according to KCONFIG. In practice,
the minimal configuration always enables a number of features, which may or
may not be disabled on some other architecture. Moreover, the list operation
ignores logical constraints that stem from KCONFIG declarations, which reduces
the number of necessary probing steps. This optimization would not have
been possible to implement using build traces, which (successfully) compiles
and links only valid configurations.
135
Appendix C. Build-System Probing
C.2. Exemplary Operation
To visualize the working mechanism of the probing algorithm, I demonstrate its
operation on two examples. The first one is a non-trivial but synthetic situation.
The second one is a realistic example taken from the F IASCO operating system.
Synthetic Example
In this section, I demonstrate the practical operation on the synthetic example
build system from Figure 3.4 on page 39. Figure C.1 shows the activated POVs
and VPs for each step. Each step shows the situation after one call to T EST P OV
is processed.
FILE3
DIR3
FILE3
DIR3
B2
DIR2
B1
B2
DIR1
B3
FILE2
DIR2
B1
A2
FILE0
DIR0
DIR1
B3
FILE2
A2
A1
FILE1
(a) Probing step 0, selection: {}
FILE0
DIR0
A1
FILE1
(b) Probing step 1, selection: {A1 }
Figure C.1.: The T EST P OV function calls the list build-system primitive, collects all the
newly selected VPs and pushes the newly found POVs onto the working
stack.
Probing Step 0: In the first step, the empty selection is probed. It activates
FILE0 and DIR0 , which are pushed onto the stack for further investigations.
Afterwards:
VP_PC = { FILE0 7→ {∅} }
POV_Stack = [ (S∅ , {FILE0 }, {DIR0 }, DIR0 ) ]
136
C.2. Exemplary Operation
Probing Step 1: Now the DIR0 is taken from the stack, the two expressions
({A1 , A2 }) are found, which results in two probing steps (Step 1 and Step 2).
First the expression A1 is fulfilled by the selection {A1 }, which additionally
activates FILE1 . Afterwards, the stack seems empty, but one additional probing
step is still pending.
Afterwards:
VP_PC = { FILE0 7→ {∅}, FILE1 7→ {{A1 }} }
POV_Stack = [ ]
FILE3
DIR3
FILE3
DIR3
B2
DIR2
B1
B2
DIR1
B3
FILE2
DIR2
B1
A2
FILE0
DIR0
DIR1
B3
FILE2
A2
A1
FILE1
(a) Probing Step 2, Selection: {A2 }
FILE0
DIR0
A1
FILE1
(b) Probing Step 3, Selection: {A2 , B1 }
Figure C.2.: In probing step 2, DIR1 is activated for the first time. This reveals the
existence of FILE3 . Adding the expression B1 activates DIR2 .
Probing Step 2: The probing step for DIR0 and the expression A2 is done by
the selection of {A2 }. This enables DIR1 , which is put onto the working stack,
and immediately FILE3 , since it is reachable by a tautology from DIR1 .
Afterwards:
VP_PC = { FILE0 7→ {∅}, FILE1 7→ {{A1 }}, FILE3 7→ {{A2 }} }
POV_Stack = [ ({A2 }, {FILE0 , FILE3 }, {DIR0 , DIR1 }, DIR1 ) ]
Probing Step 3: DIR1 has three expressions {B1 , B2 , B3 }, which will result in
three additional probing steps. First, the expression B1 evaluates to true with
the selection {A2 , B1 }. DIR2 is enabled and pushed onto the working stack.
Afterwards:
VP_PC = { FILE0 7→ {∅}, FILE1 7→ {{A1 }}, FILE3 7→ {{A2 }} }
POV_Stack = [ ({A2 , B1 }, {FILE0 , FILE3 }, {DIR0 , DIR1 , DIR2 }, DIR2 ) ]
137
Appendix C. Build-System Probing
FILE3
FILE3
DIR3
DIR3
B2
B2
DIR2
B1
DIR1
B3
FILE2
DIR2
B1
DIR0
B3
FILE2
A2
A2
FILE0
DIR1
A1
FILE1
(a) Probing Step 4, Selection: {A2 , B2 }
FILE0
DIR0
A1
FILE1
(b) Probing Step 5, Selection: {A2 , B3 }
Figure C.3.: In probing step 4, DIR3 is discovered by adding B2 . In step 5, FILE2 is
discovered by {A2 , B3 }.
Probing Step 4: B2 is the next expression. It is fulfilled by {A2 , B2 }. DIR3 is
activated and pushed onto the stack.
Afterwards:
VP_PC = { FILE0 7→ {∅}, FILE1 7→ {{A1 }}, FILE3 7→ {{A2 }} }
POV_Stack = [ ({A2 , B1 }, {FILE0 , FILE3 }, {DIR0 , DIR1 , DIR2 }, DIR2 ),
({A2 , B2 }, {FILE0 , FILE3 }, {DIR0 , DIR1 , DIR3 }, DIR3 ) ]
Probing Step 5: B3 is the next expression. It is full-filled by {A2 , B3 }. FILE2
is additionally activated by the selection, and therefore collected.
Afterwards:
VP_PC = { FILE0 7→ {∅}, FILE1 7→ {{A1 }}, FILE3 7→ {{A2 },
FILE2 7→ {{A2 , B3 }} }
POV_Stack = [ ({A2 , B1 }, {FILE0 , FILE3 }, {DIR0 , DIR1 , DIR2 }, DIR2 ),
({A2 , B2 }, {FILE0 , FILE3 }, {DIR0 , DIR1 , DIR3 }, DIR3 ) ]
The remaining items on the stack are popped, but since the to-be-examined
POVs have no further expressions and no associated VPs, no additional calls
to T EST P OV is done. Therefore the algorithm terminates, and prints out the
collected presence implications:
138
C.2. Exemplary Operation
kern/per cpu data.cpp
KConfig configuration
IMPLEMENTATION [!mp]
#ifdef CONFIG NDEBUG
...
CONFIG NDEBUG = y
CONFIG MP = y
Modules.ia32
PREPROCESS PARTS-$(CONFIG MP) += mp
...
Figure C.4.: Influence of the KCONFIG selection in the F IASCO build process: The
user selection affects the fine-grained variability in two manners. First
the selections are directly exported as CPP symbols (CONFIG_NDEBUG).
Secondly, the KCONFIG selection influences the Hohmuth flags that control
the implementation and interface blocks (CONFIG_MP controls mp).
FILE0
FILE1
FILE2
FILE3
→T
→ A1
→ A2 ∧ B3
→ A2
Exemplary Analysis of a Feature in F IASCO
As a concrete example of what the adaptions of GOLEM entail for the successful
application on F IASCO, consider the situation in Figure C.4, which depicts how
the tools work with each other. Here, the Hohmuth flag mp derives from the
KCONFIG feature CONFIG_MP. On the fine-grained level, the upper-right box
in the figure, the feature mp controls an implementation block that is only
selected if the feature is not enabled. The resulting presence implication for
this indirection, reads:
mp → CONFIG_MP
In addition to the Hohmuth preprocessor, F IASCO also implements variability
with the CPP. This can be seen in the same code example, which uses an
#ifdef block within the implementation block.
139
Appendix C. Build-System Probing
C.3. Qualitative Analysis of the Calculated Source File
Constraints
In order to show that that the presented approach produces valid, I conduct a
quantitative and qualitative comparison of my probing-based approach with
the parsing-based approaches by Nadi and Holt and Berger and She. Both are
described in detail in 3.2.2 on Page 33. To the best of my knowledge, there
are no other comparable approaches for the extraction of build-system variability at the time of writing. The use cases described in Chapter 4 constitute
additional indication about the usefulness of the approach described in this
subsection.
One way to show that the approach and implementation is correct would be
to conduct a formal proof. However since all three implementations do not
base on sound assumptions, a formal proof of correctness cannot be conducted
in a way that would be yield to verifiable results as the results could not be
mapped back to the practical implementation. Instead, I conduct a qualitative
evaluation of the extracted presence implications by comparing the resulting
propositional formulas of the competing implementations.
Because all studied models are constructed very differently, their formulas
cannot be compared textually. Therefore, they have to be analyzed semantically. Assuming that the formula for PC Mn (f ) denotes the presence condition
of the file f in the model Mn , and the two models M1 and M2 contain the
same logical constraints for a all files, then the following formula would hold:
PC M1 (f ) ↔ PC M2 (f )
f ∈ set of files ∈ M1 ∩ set of files ∈ (M2 )
When comparing the different variability models, in practice this bi-implication
holds only for some, but not for each file. In some other cases, only a simple implication PC M1 (f ) → PC M2 (f ) (or vice-versa) holds instead of the
bi-implication in the formula above. In order to investigate to what degree the
formulas agree on the variability in K BUILD, I quantify for how many files the
bi-implication, or only the simple implications holds. This is checked with a
SAT Checker.
For the implementation by Nadi and Holt, 15 percent of the 7,082 common
files have an equivalent presence implication and 81.9 percent have a presence
implication that implies the GOLEM presence implication. This shows that the
results are mostly subsumed by the GOLEM tool. This, and the fact that the
implementation does not result in additional constraints, shows that the GOLEM
approach and implementation leads both quantitatively and qualitatively better
results.
The comparison of GOLEM with K BUILD M INER shows that out of the 9,146
files Linux/x86, version 2.6.33.3, 97.7% have also a presence implication
140
C.3. Qualitative Analysis of the Calculated Source File Constraints
presence
PC K BUILD M INER (f ile) ↔ PC GOLEM (f ile)
PC K BUILD M INER (f ile) → PC GOLEM (f ile)
PC K BUILD M INER (f ile) ← PC GOLEM (f ile)
PC K BUILD M INER (f ile) 6= PC GOLEM (f ile)
Files for which both extractors produce a PC (f ile)
implications
8903
8
6
23
(99.6%)
(0.9h)
(0.7h)
(2.6h)
8940
(100%)
Table C.1.: Quantitative semantic comparison of the K BUILD extractor implementations K BUILD M INER and GOLEM. The table shows to what degree the
formulas produced by the two implementation agree or disagree with each
other.
in the result generated by GOLEM. From these common files, 99.6% have a
semantically equivalent presence implication. In 23 cases, the presence implications have no implication in either direction (i.e., none of "PC Berger (f ile) ↔
PC golem (f ile) ", "PC Berger (f ile) ← PC golem (f ile) ", "PC Berger (f ile) → PC golem (f ile)
"). The numbers in Table C.1 summarize the results and show that both methods agree on more than 99% files in Linux.
With these numbers, I demonstrate that it is feasible to extract variability
from K BUILD with both approaches. However, the results indicate that the
probing-based approach is stable with respect to the development cycle of
Linux. With parsing-based approaches, this robustness is much harder to
achieve.
141
D
Algorithms for Calculating the Configuration
Coverage
In the Configuration Coverage case study in Section 4.2, I rely on a small set of
(calculated) partial configurations that maximize the CC and CCN metrics. In
this section, I elaborate on the technical details and employed algorithms as
implemented in my prototype UNDERTAKER implementation.
Conceptually, the problem of finding a minimal set of configurations that in
total cover all #ifdef blocks in a CPP managed source file can be mapped to
solving the graph coloring problem on the “conflict graph”: All nodes represent
a conditional block. When two blocks cannot be selected at the same time, an
edge between the blocks is added that represents the conflict. The constraints
for such conflicts can stem from the CPP structure on Level l3 in Figure 2.3
on page 13, from the K BUILD on Level l2 , or the KCONFIG model on Level l1 ,
and are captured in the variability model ϕ as described in Chapter 3. The
minimal number of configurations is the number of colors required so that two
adjacent nodes have different colors. Since the complexity of this problem is
NP-hard, the goal is to find heuristics that get a suboptimal but yet sufficiently
useful solution for real-world use-cases.
My proposed approach does not operate on this conflict graph, but on a
data structure similar as depicted in Figure D.1. In the following, I present
two algorithms that approximate the solution of the problem with different
run-time complexities.
143
Appendix D. Algorithms for Calculating the Configuration Coverage
// code ...
#if defined (CONFIG_ARCH_FPGA11107)
#define TIMER3_FREQUENCY_KHZ (tmrHw_HIGH_FREQUENCY_HZ/1000 * 30)
#else
#define TIMER3_FREQUENCY_KHZ (tmrHw_HIGH_FREQUENCY_HZ/1000)
#endif
// more code ...
Block 1
Block 2
Block 3
Block 1
Block 1
↔ CONFIG_ARM
nesting
Block 2
↔ Block 1 ∧
CONFIG_ARCH_FPGA11107
alternative
Block 3
↔ Block 1 ∧ ¬Block 2
Figure D.1.: The internal structure of the conditional blocks are translated in a treelike graph. The dashed edge represents nesting and double edge represents alternatives. Each node contains the presence condition PC of the
block it represents.
D.1. The Naïve Approach
The first algorithm is depicted in Figure D.2. The general idea is to iterate over
all blocks (Line 5) and use a SAT solver to generate a configuration that selects
the current block (Line 7). As the most expensive operation is the number
of SAT queries, covered blocks in already found configurations are skipped
(Line 6). The set B collects the already covered blocks (Line 11). The resulting
set R contains the found configurations. This algorithm therefore requires n
SAT calls in the worst case.
As a further optimization, the SAT solver is tweaked to try to enable as many
propositional variables as possible while looking for satisfying assignments for
the formula, which increases the amount of selected blocks per configuration.
D.2. The Greedy Approach
On the basis of this simple algorithm, Figure D.3 shows an improved version of
the approach that more aggressively minimizes the number of configurations
and hence, improves the quality of the results. Here, the inner loop (Line 7)
144
D.2. The Greedy Approach
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
function N AIVE C OVERAGE(IN)
R ← empty set
B ← empty
Vb set
Φfile ← IN PC(b)
for all b in IN do
continue if b ∈ B
r ← sat(Φfile ∧ b)
if r.failed() then
B ← B ∪ {b}
else
B ← B ∪ r.selected_blocks()
R ← R ∪ {r.configuration()}
end if
end for
return R
end function
. List of blocks
. Found Configurations
. Selected Blocks
. all presence conditions in file
. already processed
. dead block
. mark as processed
Figure D.2.: Naïve variant
collects as many blocks as possible to the working set W S so that there is a
configuration that covers all blocks in the working set. Blocks that obviously
conflict with some other block of the working set, such as #else blocks of an
already selected #if block, are skipped in Line 9. Line 10 verifies that there is
configuration, such that all blocks of the current working set and the current
block are selected. Otherwise the block is skipped. For the found working set
of “compatible” blocks, a configuration is calculated similarly to the simple
variant (Line 19f) and added to the resulting set R (Line 21). This results in
n2 SAT calls for the worst case.
145
Appendix D. Algorithms for Calculating the Configuration Coverage
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
function G REEDY C OVERAGE(IN)
R ← empty set
B ← empty
Vb set
Φfile ← IN PC(b)
while IN.size() != B.size() do
W S ← empty set
for all b in IN do
continue if b ∈ B
continue if b conflicts W S
r ← sat(b ∧ W S ∧ Φfile )
if r.failed() then
if W S.empty() then
B ← B ∪ {b}
end if
else
W S ← W S ∪ {b}
end if
end for
r ← sat(W S ∧ Φfile )
B ← B ∪ r.selected_blocks()
R ← R ∪ {r.configuration()}
end while
return R
end function
. List of blocks
. Found Configurations
. Selected Blocks
. all presence conditions in file
Figure D.3.: Greedy variant
146
. reset working set of blocks
. already processed
. dead block
. mark as processed
. Add to working set
E
Compilation of Patches that Result from the
VAMPYR Experiments
In Section 4.2, I have presented an approach and a tool called VAMPYR that is
able to automatically apply tools for static analysis on source code with #ifdef
and #else constructs in a much more effective way compared to the today’s
state of the art. While experimenting with VAMPYR on F IASCO and Linux v3.2
and B USY B OX, I have identified a number of issues and proposed changes to
the original developers.
To illustrate the types of issues that I have found while using the VAMPYR
tool, in the following I present a selection of the resulting patches.
P1: On F IASCO, I propose the following change to fix the compilation issue
that was caused by a missing template parameter and is described in Section
5.2:
1
2
3
4
5
6
7
8
9
10
+++ b/src/kern/ux/main-ux.cpp
@@ -17,7 +17,7 @@
{
if (!Per_cpu_data_alloc::alloc(_cpu))
{
extern Spin_lock _tramp_mp_spinlock;
+
extern Spin_lock<Mword> _tramp_mp_spinlock;
printf("CPU allocation failed for CPU%u, disabling CPU.\n", _cpu);
_tramp_mp_spinlock.clear();
while (1)
P2: The icside driver fails to compile if the driver is compiled without DMA
support, which is a valid configuration according to the constraints specified
in KCONFIG. This configuration references DMA operations unconditionally
147
Appendix E. Compilation of Patches that Result from the
VAMPYR
Experiments
since at least commit 5e37bd (dated from Apr 26, 2008). I propose to add
additional #ifdef statements1 :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
+++ b/drivers/ide/icside.c
@@ -456,5 +456,7 @@ err_free:
static const struct ide_port_info icside_v6_port_info __initdata = {
.init_dma
= icside_dma_off_init,
.port_ops
= &icside_v6_no_dma_port_ops,
+#ifdef CONFIG_BLK_DEV_IDEDMA_ICS
.dma_ops
= &icside_v6_dma_ops,
+#endif
.host_flags
= IDE_HFLAG_SERIALIZE | IDE_HFLAG_MMIO,
@@ -517,7 +519,9 @@ icside_register_v6(struct icside_state *state, struct
expansion_card *ec)
ecard_set_drvdata(ec, state);
+#ifdef CONFIG_BLK_DEV_IDEDMA_ICS
if (ec->dma != NO_DMA && !request_dma(ec->dma, DRV_NAME)) {
d.init_dma = icside_dma_init;
d.port_ops = &icside_v6_port_ops;
} else
+#endif
d.dma_ops = NULL;
P3: The icside driver causes a compiler warning during the compilation
when the DMA support is enabled. Here, the first argument to printk, the
format string, requires the last argument to be an int. However, the last
argument is an unsigned long. This bug was (probably) introduced in commit
5bfb151f, dated from June 2009. I therefore propose the following change2 :
1
2
3
4
5
6
7
8
+++ b/drivers/ide/icside.c
@@ -272,5 +272,5 @@ static void icside_set_dma_mode(...)
ide_set_drivedata(drive, (void *)cycle_time);
+
printk("%s: %s selected (peak %dMB/s)\n", drive->name,
printk("%s: %s selected (peak %luMB/s)\n", drive->name,
ide_xfer_verbose(xfer_mode),
2000 / (unsigned long)ide_get_drivedata(drive));
P4:
When compiling the lp5521 LED driver, GCC emits the following warning:
drivers/leds/leds-lp5521.c:741: warning: ’buf’ may be used uninitialized
in this function
An inspection of the code reveals an code path that indeed leaves the variable
This bug was (probably) introduced in commit b3c49c, dated
from October 2011. I therefore propose the following change3 :
buf uninitialized.
1
2
3
4
+++ b/drivers/leds/leds-lp5521.c
@@ -785,7 +785,7 @@ static int __devinit lp5521_probe(...)
* LP5521_REG_ENABLE register will not have any effect
*/
1
https://lkml.org/lkml/2012/5/31/163
https://lkml.org/lkml/2012/6/5/191
3
https://lkml.org/lkml/2012/5/21/262
2
148
5
6
7
8
9
10
ret = lp5521_read(client, LP5521_REG_R_CURRENT, &buf);
if (buf != LP5521_REG_R_CURR_DEFAULT) {
if (ret == -EIO || buf != LP5521_REG_R_CURR_DEFAULT) {
dev_err(&client->dev, "error in resetting chip\n");
goto fail2;
}
+
P5: The mbx framebuffer driver for the Intel 2700G LCD controller provides
helper functions for debugging purposes that are only available if the KCONFIG
option FB_MBX_DEBUG is enabled. However, the necessary function prototypes
are missing at compilation time when the feature is enabled in KCONFIG.
Instead of introducing a header file, I propose a less intrusive solution4 to
address the issue, which has been present since the introduction of the driver
in July 2006:
1
2
3
4
5
6
7
8
9
+++ b/drivers/video/mbx/mbxfb.c
@@ -878,4 +878,7 @@ static int mbxfb_resume(...)
#ifndef CONFIG_FB_MBX_DEBUG
#define mbxfb_debugfs_init(x) do {} while(0)
#define mbxfb_debugfs_remove(x)
do {} while(0)
+#else
+void mbxfb_debugfs_init(struct fb_info *fbi);
+void mbxfb_debugfs_remove(struct fb_info *fbi);
#endif
P6: The GPIO interface driver for ARMv6 based Qualcomm MSM chips can
be compiled in configurations that lead to the following compilation failure:
drivers/gpio/gpio-msm-v1.c: In function ’msm_init_gpio’:
drivers/gpio/gpio-msm-v1.c:629: error: ’INT_GPIO_GROUP1’ undeclared
drivers/gpio/gpio-msm-v1.c:630: error: ’INT_GPIO_GROUP2’ undeclared
These identifiers are only provided for three specific MSM systems, which
are all identified by corresponding KCONFIG features. On all other ARM
systems the identifiers are not declared. My proposed change5 for this problem
problem, which lasts in Linux since September 2010 (commit 2783cc26), adds
the missing KCONFIG constraints:
1
2
3
4
5
6
7
8
9
10
+++ b/drivers/gpio/Kconfig
@@ -136,7 +136,7 @@ config GPIO_MPC8XXX
config GPIO_MSM_V1
tristate "Qualcomm MSM GPIO v1"
depends on GPIOLIB && ARCH_MSM
+
depends on GPIOLIB && ARCH_MSM && (ARCH_MSM7X00A || ARCH_MSM7X30 ||
ARCH_QSD8X50)
help
Say yes here to support the GPIO interface on
ARM v6 based Qualcomm MSM chips.
4
5
https://lkml.org/lkml/2012/6/5/467
https://lkml.org/lkml/2012/5/31/181
149
Appendix E. Compilation of Patches that Result from the
VAMPYR
Experiments
P7: In the CPPI driver, declarations from the linux/module.h header file are
used unconditionally. However, these declaration are only available indirectly.
I propose to unconditionally include the respective header file6 , which hasn’t
been done since its introduction in July 2008:
1
2
3
4
5
6
7
+++ b/drivers/usb/musb/cppi_dma.c
@@ -6,6 +6,7 @@
* The TUSB6020 has CPPI that looks much like DaVinci.
*/
+#include <linux/module.h>
#include <linux/platform_device.h>
P8: In B USY B OX the compiler warns, that some functions have format-string
security problems. In case of coreutils/stat.c it is confirmed as a bug7 .
Therefore I propose the following change8 , which has to be integrated in
B USY B OX.
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
+++ b/coreutils/stat.c
@@ -442,7 +442,7 @@ static bool do_statfs(const char *filename, const
char *format)
: getfilecon(filename, &scontext)
) < 0
) {
bb_perror_msg(filename);
+
bb_simple_perror_msg(filename);
return 0;
}
}
@@ -555,7 +555,7 @@ static bool do_stat(const char *filename, const
char *format)
: getfilecon(filename, &scontext)
) < 0
) {
bb_perror_msg(filename);
+
bb_simple_perror_msg(filename);
return 0;
}
}
+++ b/e2fsprogs/old_e2fsprogs/lsattr.c
@@ -93,7 +93,7 @@ static int lsattr_dir_proc(const char *dir_name,
struct dirent *de,
path = concat_path_file(dir_name, de->d_name);
if (lstat(path, &st) == -1)
bb_perror_msg(path);
bb_simple_perror_msg(path);
else {
if (de->d_name[0] != ’.’ || (flags & OPT_ALL)) {
list_attributes(path);
+
6
https://lkml.org/lkml/2012/5/14/221
http://lists.busybox.net/pipermail/busybox/2012-September/078360.html
8
http://lists.busybox.net/pipermail/busybox/2012-September/078473.html
7
150
Bibliography
[Ada+07a]
Bram Adams, Kris De Schutter, Herman Tromp, and Wolfgang De Meuter.
“Design recovery and maintenance of build systems”. In: Proceedings of the
23st IEEE International Conference on Software Maintainance (ICSM’07). IEEE
Computer Society Press, 2007, pages 114–123. DOI: 10.1109/ICSM.2007.
4362624.
[Ada+07b]
Bram Adams, Kris De Schutter, Herman Tromp, and Wolfgang De Meuter. “The
Evolution of the Linux Build System”. In: Electronic Communications of the
EASST (2007).
[Ada08]
Bram Adams. “Co-Evolution of Source Code and the Build System: Impact on
the Introduction of AOSD in Legacy Systems”. PhD thesis. Universiteit Gent,
2008.
[AK09]
Sven Apel and Christian Kästner. “Virtual Separation of Concerns - A Second
Chance for Preprocessors”. In: Journal of Object Technology 8.6 (2009), pages 59–
78.
[App98]
Andrew W. Appel. Modern compiler implementation in Java. Cambridge University Press, 1998.
[ASB12]
Eduardo Santana de Almeida, Christa Schwanninger, and David Benavides,
editors. Proceedings of the 16th Software Product Line Conference (SPLC ’12).
(Salvador, Brazil, Sept. 2–7, 2012). ACM Press, 2012.
[Bat04]
Don Batory. “Feature-Oriented Programming and the AHEAD Tool Suite”. In:
Proceedings of the 26th International Conference on Software Engineering (ICSE
’04). IEEE Computer Society Press, 2004, pages 702–703.
[Bax02]
Ira D. Baxter. “DMS: program transformations for practical scalable software
evolution”. In: Proceedings of the 5th International Workshop on Principles of
Software Evolution (IWPSE’02). (Orlando, FL, USA). ACM Press, 2002, pages 48–
51. DOI: 10.1145/512035.512047.
[Ben+06]
D. Benavides, S. Segura, P. Trinidad, and A. Ruiz-Cortés. “A first step towards
a framework for the automated analysis of feature models”. In: Managing
Variability for Software Product Lines: Working With Variability Mechanisms.
2006.
[Ben+07]
David Benavides, Sergio Segura, Pablo Trinidad, and Antonio Ruiz-Cortés.
“FAMA: Tooling a Framework for the Automated Analysis of Feature Models”.
In: Proceedings of the 1th International Workshop on Variability Modelling of
Software-intensive Systems (VAMOS ’07). 2007.
151
Bibliography
[Ber+10a]
Thorsten Berger, Steven She, Krzysztof Czarnecki, and Andrzej Wasowski.
Feature-to-Code Mapping in Two Large Product Lines. Technical report. University of Leipzig (Germany), University of Waterloo (Canada), IT University of
Copenhagen (Denmark), 2010.
[Ber+10b]
Thorsten Berger, Steven She, Rafael Lotufo, Krzysztof Czarnecki, and Andrzej
Wasowski. “Feature-to-code mapping in two large product lines”. In: Proceedings
of the 14th Software Product Line Conference (SPLC ’10). (Jeju Island, South
Korea). Edited by Kyo Kang. Volume 6287. Lecture Notes in Computer Science.
Poster session. Springer-Verlag, 2010, pages 498–499.
[Ber+10c]
Thorsten Berger, Steven She, Rafael Lotufo, and Andrzej Wasowski und Krzysztof
Czarnecki. “Variability Modeling in the Real: A Perspective from the Operating
Systems Domain”. In: Proceedings of the 25th IEEE/ACM International Conference
on Automated Software Engineering (ASE ’10). (Antwerp, Belgium). Edited by
Charles Pecheur, Jamie Andrews, and Elisabetta Di Nitto. ACM Press, 2010,
pages 73–82. DOI: 10.1145/1858996.1859010.
[Ber+12a]
Thorsten Berger, Steven She, Rafael Lotufo, and Andrzej Wasowski und Krzysztof
Czarnecki. Variability Modeling in the Systems Software Domain. Technical report GSDLAB-TR 2012-07-06. Generative Software Development Laboratory,
University of Waterloo, 2012. URL: http://gsd.uwaterloo.ca/sites/
default/files/vm-2012-berger.pdf.
[Ber+12b]
Thorsten Berger, Steven She, Rafael Lotufo, and Andrzej Wasowski und Krzysztof
Czarnecki. Variability Modeling in the Systems Software Domain. Technical report. GSDLAB-TR 2012-07-06. Generative Software Development Laboratory,
University of Waterloo, 2012.
[Bes+10]
Al Bessey, Ken Block, Ben Chelf, Andy Chou, Bryan Fulton, Seth Hallem, Charles
Henri-Gros, Asya Kamsky, Scott McPeak, and Dawson Engler. “A few billion
lines of code later: using static analysis to find bugs in the real world”. In: Communications of the ACM 53 (2 2010), pages 66–75. DOI: 10.1145/1646353.
1646374.
[Beu06]
Danilo Beuche. Variant Management with pure::variants. Technical report. http:
//www.pure-systems.com/fileadmin/downloads/pv-whitepaperen-04.pdf, visited 2011-11-12. pure-systems GmbH, 2006.
[Bie08]
Armin Biere. “PicoSAT Essentials”. In: Journal on Satisfiability, Boolean Modeling
and Computation (JSAT) 4 (2008), pages 75–97.
[BM01]
Ira D. Baxter and Michael Mehlich. “Preprocessor Conditional Removal by
Simple Partial Evaluation”. In: Proceedings of the 8th Working Conference on
Reverse Engineering (WCRE ’01). IEEE Computer Society Press, 2001, page 281.
[BN00]
Greg J Badros and David Notkin. “A framework for preprocessor-aware C source
code analyses”. In: Software: Practice and Experience 30.8 (2000), pages 907–
924. DOI: 10.1002/(SICI)1097- 024X(20000710)30:8<907::AIDSPE324>3.3.CO;2-9.
[Bor09]
Anton Borisov. “Coreboot at your service!” In: Linux Journal 1 (186 2009).
[BRCT05]
D. Benavides, A. Ruiz-Cortés, and P. Trinidad. “Automated Reasoning on Feature
Models”. In: Proceedings of the 17th International Conference on Advanced Information Systems Engineering (CAISE ’05). Volume 3520. Springer-Verlag, 2005,
pages 491–503.
152
Bibliography
[BS]
Thorsten Berger and Steven She. Google Code Project: various variability extraction and analysis tools. URL: http://code.google.com/p/variability/
(visited on 02/16/2012).
[Bus]
BusyBox Project Homepage. URL: http://www.busybox.net/ (visited on
05/11/2012).
[CAB11]
Lianping Chen and Muhammad Ali Babar. “A systematic review of evaluation of
variability management approaches in software product lines”. In: Information
and Software Technology 53.4 (2011), pages 344–362. DOI: 10 . 1016 / j .
infsof.2010.12.006.
[Cam+93]
Roy Campbell, Nayeem Islam, Peter Madany, and David Raila. “Designing and
Implementing Choices: An Object-Oriented System in C++”. In: Communications of the ACM 36.9 (1993), pages 117–126. DOI: 10 . 1145 / 162685 .
162717.
[CE00]
Krysztof Czarnecki and Ulrich W. Eisenecker. Generative Programming. Methods,
Tools and Applications. Addison-Wesley, 2000.
[Cha09]
Scott Chacon. Pro Git. 1st. Apress, 2009.
[Chi11]
Shigeru Chiba, editor. Proceedings of the 10th International Conference on AspectOriented Software Development (AOSD ’11). (Porto de Galinhas, Brazil). ACM
Press, 2011.
[Cho+01]
Andy Chou, Junfeng Yang, Benjamin Chelf, Seth Hallem, and Dawson Engler.
“An empirical study of operating systems errors”. In: Proceedings of the 18th
ACM Symposium on Operating Systems Principles (SOSP ’01). (Banff, Alberta,
Canada). Edited by Keith Marzullo and M. Satyanarayanan. ACM Press, 2001,
pages 73–88. DOI: 10.1145/502034.502042.
[CK03]
Yvonne Coady and Gregor Kiczales. “Back to the Future: A Retroactive Study
of Aspect Evolution in Operating System Code”. In: Proceedings of the 2nd
International Conference on Aspect-Oriented Software Development (AOSD ’03).
Edited by Mehmet Akşit. ACM Press, 2003, pages 50–59.
[CKHM12]
Jonathan Corbet, Greg Kroah-Hartman, and Amanda McPherson. Linux Kernel
Development. How Fast it is Going, Who is Doing It, What They are Doing, and
Who is Sponsoring It. The Linux Foundation, 2012.
[CN01]
Paul Clements and Linda Northrop. Software Product Lines: Practices and Patterns.
Addison-Wesley, 2001.
[Coh+96]
D.M. Cohen, S.R. Dalal, J. Parelius, and G.C. Patton. “The combinatorial design
approach to automatic test generation”. In: IEEE Software 13.5 (1996), pages 83–
88. DOI: 10.1109/52.536462.
[Cok]
Russell Coker. Bonnie++. Benchmark suite for hard drive and file system performance. URL: http : / / www . coker . com . au / bonnie + +/ (visited on
08/02/2012).
[Cor99]
The MITRE Corporation. Common Vulnerabilities and Exposures. 1999–. URL:
http://cvs.mitre.org (visited on 01/31/2013).
153
Bibliography
[CP06]
Krzysztof Czarnecki and Krzysztof Pietroszek. “Verifying feature-based model
templates against well-formedness OCL constraints”. In: Proceedings of the 6th
International Conference on Generative Programming and Component Engineering
(GPCE ’06). (Portland, OR, USA). ACM Press, 2006, pages 211–220. DOI: 10.
1145/1173706.1173738.
[Cri+07]
John Criswell, Andrew Lenharth, Dinakar Dhurjati, and Vikram Adve. “Secure
Virtual Architecture: A Safe Execution Environment for Commodity Operating
Systems”. In: Proceedings of the 21st ACM Symposium on Operating Systems
Principles (SOSP ’07). (Stevenson, WA, USA). ACM Press, 2007, pages 351–366.
DOI : 10.1145/1294261.1294295.
[CW07]
Krzysztof Czarnecki and Andrzej Wasowski. “Feature Diagrams and Logics:
There and Back Again”. In: Proceedings of the 11th Software Product Line Conference (SPLC ’07). IEEE Computer Society Press, 2007, pages 23–34. DOI:
10.1109/SPLINE.2007.24.
[Die+12a]
Christian Dietrich, Reinhard Tartler, Wolfgang Schröder-Preikschat, and Daniel
Lohmann. “A Robust Approach for Variability Extraction from the Linux Build
System”. In: Proceedings of the 16th Software Product Line Conference (SPLC ’12).
(Salvador, Brazil, Sept. 2–7, 2012). Edited by Eduardo Santana de Almeida,
Christa Schwanninger, and David Benavides. ACM Press, 2012, pages 21–30.
DOI : 10.1145/2362536.2362544.
[Die+12b]
Christian Dietrich, Reinhard Tartler, Wolfgang Schröder-Preikschat, and Daniel
Lohmann. “Understanding Linux Feature Distribution”. In: Proceedings of the 2nd
AOSD Workshop on Modularity in Systems Software (AOSD-MISS ’12). (Potsdam,
Germany, Mar. 27, 2012). Edited by Christoph Borchert, Michael Haupt, and
Daniel Lohmann. ACM Press, 2012. DOI: 10.1145/2162024.2162030.
[DPM02]
G. Denys, F. Piessens, and F. Matthijs. “A Survey of Customizability in Operating
Systems Research”. In: ACM Computing Surveys 34.4 (2002), pages 450–468.
[Ear]
Linux Early Userspace Documentation. 2011. URL: http://www.kernel.org/
doc/Documentation/early-userspace/.
[EBN02]
Michael D. Ernst, Greg J. Badros, and David Notkin. “An Empirical Analysis of C
Preprocessor Use”. In: IEEE Transactions on Software Engineering 28.12 (2002),
pages 1146–1170. DOI: 10.1109/TSE.2002.1158288.
[Els+10]
Christoph Elsner, Peter Ulbrich, Daniel Lohmann, and Wolfgang SchröderPreikschat. “Consistent Product Line Configuration Across File Type and Product
Line Boundaries”. In: Proceedings of the 14th Software Product Line Conference
(SPLC ’10). (Jeju Island, South Korea). Edited by Kyo Kang. Volume 6287.
Lecture Notes in Computer Science. Springer-Verlag, 2010, pages 181–195. DOI:
10.1007/978-3-642-15579-6_13.
[Eng+01]
Dawson Engler, David Yu Chen, Seth Hallem, Andy Chou, and Benjamin Chelf.
“Bugs as deviant behavior: a general approach to inferring errors in systems
code”. In: Proceedings of the 18th ACM Symposium on Operating Systems Principles (SOSP ’01). (Banff, Alberta, Canada). Edited by Keith Marzullo and M. Satyanarayanan. ACM Press, 2001, pages 57–72. DOI: 10.1145/502059.502041.
154
Bibliography
[Ern+00]
Michael D. Ernst, Adam Czeisler, William G. Griswold, and David Notkin.
“Quickly detecting relevant program invariants”. In: Proceedings of the 22nd
International Conference on Software Engineering (ICSE ’00). (Limerick, Ireland).
ACM Press, 2000, pages 449–458. DOI: 10.1145/337180.337240.
[EW11]
Martin Erwig and Eric Walkingshaw. “The Choice Calculus: A Representation for
Software Variation”. In: ACM Transactions on Software Engineering Methodology
21.1 (2011), 6:1–6:27. DOI: 10.1145/2063239.2063245.
[Fas+02]
Jean-Philippe Fassino, Jean-Bernard Stefani, Julia Lawall, and Gilles Muller.
“THINK: A Software Framework for Component-based Operating System Kernels”. In: Proceedings of the 2002 USENIX Annual Technical Conference. USENIX
Association, 2002, pages 73–86.
[Fia]
Fiasco Project Homepage. URL: http://os.inf.tu-dresden.de/fiasco/
(visited on 05/11/2012).
[Fri+01]
L. Fernando Friedrich, John Stankovic, Marty Humphrey, Michael Marley, and
John Haskins. “A Survey of Configurable, Component-Based Operating Systems
for Embedded Applications”. In: IEEE Micro 21.3 (2001), pages 54–68.
[Gar05]
Alejandra Garrido. “Program refactoring in the presence of preprocessor directives”. Adviser-Johnson, Ralph. PhD thesis. University of Illinois at UrbanaChampaign, 2005.
[GG12]
Paul Gazzillo and Robert Grimm. “SuperC: parsing all of C by taming the
preprocessor”. In: Proceedings of the ACM SIGPLAN Conference on Programming
Language Design and Implementation (PLDI ’12). (Beijing, China). ACM Press,
2012, pages 323–334. DOI: 10.1145/2254064.2254103.
[Gnu]
GNU Coding Standards – GNU Project – Free Software Foundation (FSF). URL:
http://www.gnu.org/prep/standards/standards.html (visited on
11/12/2011).
[Goh]
Andreas Gohr. DokuWiki. URL: http://dokuwiki.org (visited on 06/03/2012).
[Goo09]
Google. Seccomp Sandbox for Linux. 2009. URL: http://code.google.com/
p/seccompsandbox/wiki/overview (visited on 06/05/2012).
[GR03]
Kai Germaschewski and Sam Ravnborg. “Kernel configuration and building in
Linux 2.5”. In: Proceedings of the Linux Symposium. (Ottawa, Ontario, Canada).
2003, pages 185–200.
[GS04]
Jack Greenfield and Keith Short. Software Factories. John Wiley & Sons, Inc.,
2004.
[Har13]
Darren Hart. Yocto Project Linux Kernel Development Manual. The Linux Foundation, 2013. URL: http://www.yoctoproject.org/docs/1.4/kerneldev/kernel-dev.html (visited on 04/13/2013).
[Hoh05]
Michael Hohmuth. Preprocess. A preprocessor for C and C++ modules. 2005.
URL : http://os.inf.tu-dresden.de/~hohmuth/prj/preprocess/
(visited on 09/04/2012).
[Hu+00]
Ying Hu, Ettore Merlo, Michel Dagenais, and Bruno Lagüe. “C/C++ Conditional
Compilation Analysis Using Symbolic Execution”. In: Proceedings of the 16th IEEE
International Conference on Software Maintainance (ICSM’00). IEEE Computer
Society Press, 2000, page 196.
155
Bibliography
[JGS93]
Neil D. Jones, Carsten K. Gomard, and Peter Sestoft. Partial evaluation and
automatic program generation. Prentice Hall PTR, 1993.
[Jin+12]
Guoliang Jin, Wei Zhang, Dongdong Deng, Ben Liblit, and Shan Lu. “Automated
Concurrency-Bug Fixing”. In: 10th Symposium on Operating System Design and
Implementation (OSDI ’12). (Hollywood, CA, USA). USENIX Association, 2012.
[Kan+90]
Kyo Kang, Sholom Cohen, James Hess, William Novak, and A. Spencer Peterson.
Feature-Oriented Domain Analysis (FODA) Feasibility Study. Technical report.
Carnegie Mellon University, Software Engineering Institute, 1990.
[Kan10]
Kyo Kang, editor. Proceedings of the 14th Software Product Line Conference (SPLC
’10). (Jeju Island, South Korea). Volume 6287. Lecture Notes in Computer
Science. Springer-Verlag, 2010.
[Kic+97]
Gregor Kiczales, John Lamping, Anurag Mendhekar, Chris Maeda, Cristina
Videira Lopes, Jean-Marc Loingtier, and John Irwin. “Aspect-Oriented Programming”. In: Proceedings of the 11th European Conference on Object-Oriented
Programming (ECOOP ’97). (Finland). Edited by Mehmet Aksit and Satoshi
Matsuoka. Volume 1241. Lecture Notes in Computer Science. Springer-Verlag,
1997, pages 220–242.
[Kim+08]
Chang Kim, Hwan Peter, Christian Kästner, and Don Batory. “On the Modularity
of Feature Interactions”. In: Proceedings of the 5th International Conference on
Generative Programming and Component Engineering (GPCE ’08). (Nashville, TN,
USA). ACM Press, 2008, pages 23–34. DOI: 10.1145/1449913.1449919.
[Kre+06]
Ted Kremenek, Paul Twohey, Godmar Back, Andrew Ng, and Dawson Engler.
“From uncertainty to belief: inferring the specification within”. In: 7th Symposium on Operating System Design and Implementation (OSDI ’06). (Seattle, WA,
USA). USENIX Association, 2006, pages 161–176.
[Kru07]
Charles W. Krueger. “BigLever Software Gears and the 3-tiered SPL Methodology”. In: Companion to the 22nd ACM SIGPLAN Conference on Object-Oriented
Programming Systems and Applications (OOPSLA ’07). (Montreal, Quebec, Canada).
ACM Press, 2007, pages 844–845. DOI: 10.1145/1297846.1297918.
[KSK11]
Anil Kurmus, Alessandro Sorniotti, and Rüdiger Kapitza. “Attack surface reduction for commodity OS kernels: trimmed garden plants may attract less bugs”.
In: Proceedings of the 4th European Workshop on system security (EUROSEC
’11). (Salzburg, Austria). ACM Press, 2011, 6:1–6:6. DOI: 10.1145/1972551.
1972557.
[Kur+13]
Anil Kurmus, Reinhard Tartler, Daniela Dorneanu, Bernhard Heinloth, Valentin
Rothberg, Andreas Ruprecht, Wolfgang Schröder-Preikschat, Daniel Lohmann,
and Rüdiger Kapitza. “Attack Surface Metrics and Automated Compile-Time OS
Kernel Tailoring”. In: Proceedings of the 20th Network and Distributed Systems
Security Symposium. (San Diego, CA, USA, Feb. 24–27, 2013). The Internet
Society, 2013. URL: http://www4.cs.fau.de/Publications/2013/
kurmus_13_ndss.pdf.
156
Bibliography
[Käs+11]
Christian Kästner, Paolo G. Giarrusso, Tillmann Rendel, Sebastian Erdweg, Klaus
Ostermann, and Thorsten Berger. “Variability-Aware Parsing in the Presence of
Lexical Macros and Conditional Compilation”. In: Proceedings of the 26th ACM
Conference on Object-Oriented Programming, Systems, Languages, and Applications (OOPSLA ’11). (Portland). ACM Press, 2011. DOI: 10.1145/2048066.
2048128.
[LA04]
Chris Lattner and Vikram Adve. “LLVM: A Compilation Framework for Lifelong
Program Analysis & Transformation”. In: Proceedings of the 2004 International
Symposium on Code Generation and Optimization (CGO’04). (Palo Alto, CA,
USA). IEEE Computer Society Press, 2004.
[Lat04]
Mario Latendresse. “Rewrite Systems for Symbolic Evaluation of C-like Preprocessing”. In: CSMR ’04: Proceedings of the Eighth Euromicro Working Conference
on Software Maintenance and Reengineering (CSMR’04). IEEE Computer Society
Press, 2004, page 165.
[Lee+04]
C.T. Lee, J.M. Lin, Z.W. Hong, and W.T. Lee. “An Application-Oriented Linux
Kernel Customization for Embedded Systems”. In: Journal of information science
and engineering 20.6 (2004), pages 1093–1108.
[Lie+10]
Jörg Liebig, Sven Apel, Christian Lengauer, Christian Kästner, and Michael
Schulze. “An Analysis of the Variability in Forty Preprocessor-Based Software
Product Lines”. In: Proceedings of the 32nd International Conference on Software
Engineering (ICSE ’10). (Cape Town, South Africa). ACM Press, 2010. DOI:
10.1145/1806799.1806819.
[LKA11]
Jörg Liebig, Christian Kästner, and Sven Apel. “Analyzing the discipline of
preprocessor annotations in 30 million lines of C code”. In: Proceedings of the
10th International Conference on Aspect-Oriented Software Development (AOSD
’11). (Porto de Galinhas, Brazil). Edited by Shigeru Chiba. ACM Press, 2011,
pages 191–202. DOI: 10.1145/1960275.1960299.
[Loh+06]
Daniel Lohmann, Fabian Scheler, Reinhard Tartler, Olaf Spinczyk, and Wolfgang
Schröder-Preikschat. “A Quantitative Analysis of Aspects in the eCos Kernel”.
In: Proceedings of the ACM SIGOPS/EuroSys European Conference on Computer
Systems 2006 (EuroSys ’06). (Leuven, Belgium). Edited by Yolande Berbers and
Willy Zwaenepoel. ACM Press, 2006, pages 191–204. DOI: 10.1145/1218063.
1217954.
[Loh+09]
Daniel Lohmann, Wanja Hofer, Wolfgang Schröder-Preikschat, Jochen Streicher,
and Olaf Spinczyk. “CiAO: An Aspect-Oriented Operating-System Family for
Resource-Constrained Embedded Systems”. In: Proceedings of the 2009 USENIX
Annual Technical Conference. USENIX Association, 2009, pages 215–228. URL:
http://www.usenix.org/event/usenix09/tech/full_papers/
lohmann/lohmann.pdf.
[Loh+11]
Daniel Lohmann, Wanja Hofer, Wolfgang Schröder-Preikschat, and Olaf Spinczyk.
“Aspect-Aware Operating-System Development”. In: Proceedings of the 10th International Conference on Aspect-Oriented Software Development (AOSD ’11). (Porto
de Galinhas, Brazil). Edited by Shigeru Chiba. ACM Press, 2011, pages 69–80.
DOI : 10.1145/1960275.1960285.
157
Bibliography
[Loh+12]
Daniel Lohmann, Olaf Spinczyk, Wanja Hofer, and Wolfgang Schröder-Preikschat.
“The Aspect-Aware Design and Implementation of the CiAO Operating-System
Family”. In: Transactions on AOSD IX. Edited by Gary T. Leavens and Shigeru
Chiba. Lecture Notes in Computer Science 7271. (To Appear). Springer-Verlag,
2012, pages 168–215. DOI: 10.1007/978-3-642-35551-6_5.
[Loh09]
Daniel Lohmann. “Aspect Awareness in the Development of Configurable System Software”. PhD thesis. Friedrich-Alexander University Erlangen-Nuremberg,
2009. URL: http://www.opus.ub.uni-erlangen.de/opus/volltexte/
2009/1328/pdf/LohmannDissertation.pdf.
[Lot+10]
Rafael Lotufo, Steven She, Thorsten Berger, Krzysztof Czarnecki, and Andrzej
Wasowski. “Evolution of the Linux Kernel Variability Model”. In: Proceedings
of the 14th Software Product Line Conference (SPLC ’10). (Jeju Island, South
Korea). Edited by Kyo Kang. Volume 6287. Lecture Notes in Computer Science.
Springer-Verlag, 2010, pages 136–150.
[LWE11]
Duc Le, Eric Walkingshaw, and Martin Erwig. “#ifdef confirmed harmful: Promoting understandable software variation”. In: IEEE International Symposium
on Visual Languages and Human-Centric Computing. 2011, pages 143–150. DOI:
10.1109/VLHCC.2011.6070391.
[LZ05]
Zhenmin Li and Yuanyuan Zhou. “PR-Miner: automatically extracting implicit
programming rules and detecting violations in large software code”. In: Proceedings of the 10th European Software Engineering Conference and the 13th ACM Symposium on the Foundations of Software Engineering (ESEC/FSE ’00). (Lisbon, Portugal). ACM Press, 2005, pages 306–315. DOI: 10.1145/1081706.1081755.
[McI+11]
Shane McIntosh, Bram Adams, Thanh H.D. Nguyen, Yasutaka Kamei, and Ahmed
E. Hassan. “An empirical study of build maintenance effort”. In: Proceedings of
the 33nd International Conference on Software Engineering (ICSE ’11). (Waikiki,
Honolulu). Edited by Richard N. Taylor, Harald Gall, and Nenad Medvidović.
ACM Press, 2011, pages 141–150. DOI: 10.1145/1985793.1985813.
[Met+07]
Andreas Metzger, Patrick Heymans, Klaus Pohl, Pierre-Yves Schobbens, and
Germain Saval. “Disambiguating the Documentation of Variability in Software
Product Lines: A Separation of Concerns, Formalization and Automated Analysis”. In: Proceedings of the 15th IEEE Conference on Requirements Engineering
(RE ’07). (New Delhi, India, Oct. 15–19, 2007). IEEE Computer Society, 2007,
pages 243–253. DOI: 10.1109/RE.2007.61.
[MJ98]
David Mosberger and Tai Jin. “httperf. A tool for measuring web server performance”. In: SIGMETRICS Performance Evaluation Review 26.3 (1998), pages 31–
37. DOI: 10.1145/306225.306235.
[MO04]
Mira Mezini and Klaus Ostermann. “Variability Management with FeatureOriented Programming and Aspects”. In: Proceedings of ACM SIGSOFT ’04 /
FSE-12. (Newport Beach, CA, USA). 2004.
[MS01]
Keith Marzullo and M. Satyanarayanan, editors. Proceedings of the 18th ACM
Symposium on Operating Systems Principles (SOSP ’01). (Banff, Alberta, Canada).
ACM Press, 2001.
158
Bibliography
[Nad+13]
Sarah Nadi, Christian Dietrich, Reinhard Tartler, Ric Holt, and Daniel Lohmann.
“Linux Variability Anomalies: What Causes Them and How Do They Get Fixed?”
In: Proceedings of the 10th Working Conference on Mining Software Repositories
(MSR ’13). (to appear). 2013.
[NBE12]
Alexander Nöhrer, Armin Biere, and Alexander Egyed. “A comparison of strategies for tolerating inconsistencies during decision-making”. In: Proceedings of the
16th Software Product Line Conference (SPLC ’12). (Salvador, Brazil, Sept. 2–7,
2012). Edited by Eduardo Santana de Almeida, Christa Schwanninger, and
David Benavides. ACM Press, 2012, pages 11–20. DOI: 10.1145/2362536.
2362543.
[NC01]
Linda Northrop and Paul Clements. Software Product Lines: Practices and Patterns.
Addison-Wesley, 2001.
[NH11]
Sarah Nadi and Richard C. Holt. “Make it or Break it: Mining Anomalies from
Linux Kbuild”. In: Proceedings of the 18th Working Conference on Reverse Engineering (WCRE ’11). 2011, pages 315–324. DOI: 10.1109/WCRE.2011.46.
[NH12]
Sarah Nadi and Richard C. Holt. “Mining Kbuild to Detect Variability Anomalies in Linux”. In: Proceedings of the 16th European Conference on Software
Maintenance and Reengineering (CSMR ’12). (Szeged, Hungary, Mar. 27–30,
2012). Edited by Tom Mens, Yiannis Kanellopoulos, and Andreas Winter. IEEE
Computer Society Press, 2012. DOI: 10.1109/CSMR.2012.21.
[OMR10]
Sebastian Oster, Florian Markert, and Philipp Ritter. “Automated Incremental
Pairwise Testing of Software Product Lines”. In: Proceedings of the 14th Software
Product Line Conference (SPLC ’10). (Jeju Island, South Korea). Edited by Kyo
Kang. Volume 6287. Lecture Notes in Computer Science. Springer-Verlag, 2010,
pages 196–210. DOI: 10.1007/978-3-642-15579-6_14.
[Pad+08]
Yoann Padioleau, Julia L. Lawall, Gilles Muller, and René Rydhof Hansen.
“Documenting and Automating Collateral Evolutions in Linux Device Drivers”.
In: Proceedings of the ACM SIGOPS/EuroSys European Conference on Computer
Systems 2008 (EuroSys ’08). (Glasgow, Scotland). ACM Press, 2008.
[Pal+11]
Nicolas Palix, Gaël Thomas, Suman Saha, Christophe Calvès, Julia L. Lawall,
and Gilles Muller. “Faults in Linux: Ten years later”. In: Proceedings of the 16th
International Conference on Architectural Support for Programming Languages
and Operating Systems (ASPLOS ’11). ACM Press, 2011, pages 305–318. DOI:
10.1145/1950365.1950401.
[Par76]
David Lorge Parnas. “On the Design and Development of Program Families”. In:
IEEE Transactions on Software Engineering SE-2.1 (1976), pages 1–9.
[Par79]
David Lorge Parnas. “Designing Software for Ease of Extension and Contraction”.
In: IEEE Transactions on Software Engineering SE-5.2 (1979), pages 128–138.
[PBL05]
Klaus Pohl, Günter Böckle, and Frank J. van der Linden. Software Product Line
Engineering: Foundations, Principles and Techniques. Springer-Verlag, 2005.
[Php]
phpBB. Free and Open Source Forum Software. URL: www.phpbb.com (visited
on 06/03/2012).
159
Bibliography
[PLM10]
Nicolas Palix, Julia Lawall, and Gilles Muller. “Tracking code patterns over
multiple software versions with Herodotos”. In: Proceedings of the 9th International Conference on Aspect-Oriented Software Development (AOSD ’10). (Rennes
and Saint-Malo, France). ACM Press, 2010, pages 169–180. DOI: 10.1145/
1739230.1739250.
[Rei+00]
Alastair Reid, Matthew Flatt, Leigh Stoller, Jay Lepreau, and Eric Eide. “Knit:
Component Composition for Systems Software”. In: 4th Symposium on Operating
System Design and Implementation (OSDI ’00). (San Diego, CA, USA). USENIX
Association, 2000, pages 347–360.
[Ryz+09]
Leonid Ryzhyk, Peter Chubb, Ihor Kuz, and Gernot Heiser. “Dingo: Taming
Device Drivers”. In: Proceedings of the fourth ACM European Conference on
Computer Systems (EuroSys’09), Nuremberg, Germany, March 31–April 3, 2009.
ACM Press, 2009, pages 275–288.
[SB10]
Steven She and Thorsten Berger. Formal Semantics of the Kconfig Language.
Technical Note. University of Waterloo, 2010.
[SC92]
Henry Spencer and Gehoff Collyer. “#ifdef Considered Harmful, or Portability
Experience With C News”. In: Proceedings of the 1992 USENIX Annual Technical
Conference. (San Antonio, TX, USA). USENIX Association, 1992.
[She+11]
Steven She, Rafael Lotufo, Thorsten Berger, Andrzej Wasowski, and Krzysztof
Czarnecki. “Reverse Engineering Feature Models”. In: Proceedings of the 33nd
International Conference on Software Engineering (ICSE ’11). (Waikiki, Honolulu).
Edited by Richard N. Taylor, Harald Gall, and Nenad Medvidović. ACM Press,
2011, pages 461–470. DOI: 10.1145/1985793.1985856.
[Sin+10]
Julio Sincero, Reinhard Tartler, Daniel Lohmann, and Wolfgang SchröderPreikschat. “Efficient Extraction and Analysis of Preprocessor-Based Variability”. In: Proceedings of the 9th International Conference on Generative Programming and Component Engineering (GPCE ’10). (Eindhoven, The Netherlands).
Edited by Eelco Visser and Jaakko Järvi. ACM Press, 2010, pages 33–42. DOI:
10.1145/1868294.1868300.
[Sin13]
Julio Sincero. “Variability Bugs in System Software”. PhD thesis. FriedrichAlexander University Erlangen-Nuremberg, 2013.
[SMS10]
Richard M. Stallman, Roland McGrath, and Paul D. Smith. GNU make manual.
A Program for Directing Recompilation. Free Software Foundation. GNU Press,
2010.
[Sos]
Proceedings of the 21st ACM Symposium on Operating Systems Principles (SOSP
’07). (Stevenson, WA, USA). ACM Press, 2007.
[Spe]
Brad Spengler. Grsecurity Linux kernel patches. URL: http://grsecurity.
net (visited on 09/24/2012).
[Spi08]
Diomidis Spinellis. “A Tale of Four Kernels”. In: Proceedings of the 30th International Conference on Software Engineering (ICSE ’08). (Leipzig, Germany).
Edited by Wilhem Schäfer, Matthew B. Dwyer, and Volker Gruhn. ACM Press,
2008, pages 381–390. DOI: 10.1145/1368088.1368140.
160
Bibliography
[SRG11]
Klaus Schmid, Rick Rabiser, and Paul Grünbacher. “A comparison of decision
modeling approaches in product lines”. In: Proceedings of the 5th International
Workshop on Variability Modelling of Software-intensive Systems (VAMOS ’11).
Edited by Patrick Heymans, Krzysztof Czarnecki, and Ulrich W. Eisenecker. ACM
Press, 2011, pages 119–126. DOI: 10.1145/1944892.1944907.
[Tam+12]
Ahmed Tamrawi, Hoan Anh Nguyen, Hung Viet Nguyen, and Tien N. Nguyen.
“Build code analysis with symbolic evaluation”. In: Proceedings of the 34nd International Conference on Software Engineering (ICSE ’12). (Zurich, Switzerland).
IEEE Computer Society Press, 2012, pages 650–660. DOI: 10.1109/ICSE.
2012.6227152.
[Tan+07]
Lin Tan, Ding Yuan, Gopal Krishna, and Yuanyuan Zhou. “/*iComment: Bugs
or Bad Comments?*/”. In: Proceedings of the 21st ACM Symposium on Operating Systems Principles (SOSP ’07). (Stevenson, WA, USA). ACM Press, 2007,
pages 145–158. DOI: 10.1145/1294261.1294276.
[Tar+11a]
Reinhard Tartler, Daniel Lohmann, Christian Dietrich, Christoph Egger, and
Julio Sincero. “Configuration Coverage in the Analysis of Large-Scale System
Software”. In: Proceedings of the 6th Workshop on Programming Languages and
Operating Systems (PLOS ’11). (Cascais, Portugal). Edited by Eric Eide, Gilles
Muller, Olaf Spinczyk, and Wolfgang Schröder-Preikschat. ACM Press, 2011,
2:1–2:5. DOI: 10.1145/2039239.2039242.
[Tar+11b]
Reinhard Tartler, Daniel Lohmann, Julio Sincero, and Wolfgang SchröderPreikschat. “Feature Consistency in Compile-Time-Configurable System Software: Facing the Linux 10,000 Feature Problem”. In: Proceedings of the ACM
SIGOPS/EuroSys European Conference on Computer Systems 2011 (EuroSys ’11).
(Salzburg, Austria). Edited by Christoph M. Kirsch and Gernot Heiser. ACM
Press, 2011, pages 47–60. DOI: 10.1145/1966445.1966451.
[Tar+12]
Reinhard Tartler, Anil Kurmus, Bernard Heinloth, Valentin Rothberg, Andreas
Ruprecht, Daniela Doreanu, Rüdiger Kapitza, Wolfgang Schröder-Preikschat,
and Daniel Lohmann. “Automatic OS Kernel TCB Reduction by Leveraging
Compile-Time Configurability”. In: Proceedings of the 8th International Workshop
on Hot Topics in System Dependability (HotDep ’12). (Los Angeles, CA, USA).
USENIX Association, 2012, pages 1–6.
[TGM11]
Richard N. Taylor, Harald Gall, and Nenad Medvidović, editors. Proceedings of
the 33nd International Conference on Software Engineering (ICSE ’11). (Waikiki,
Honolulu). ACM Press, 2011.
[Tha+07]
Sahil Thaker, Don Batory, David Kitchin, and William Cook. “Safe composition
of product lines”. In: Proceedings of the 7th International Conference on Generative
Programming and Component Engineering (GPCE ’07). (Salzburg, Austria). ACM
Press, 2007, pages 95–104. DOI: 10.1145/1289971.1289989.
[Tor03]
Linus Torvalds. Sparse - a Semantic Parser for C. 2003. URL: http://www.
kernel.org/pub/software/devel/sparse/.
[Tou05]
Jean-Charles Tournier. A Survey of Configurable Operating Systems. Technical
report TR-CS-2005-43. http://www.cs.unm.edu/~treport/tr/0512/Tournier.pdf. University of New Mexico, 2005.
161
Bibliography
[VV11]
Markus Völter and Eelco Visser. “Product Line Engineering using DomainSpecific languages”. In: Proceedings of the 15th Software Product Line Conference
(SPLC ’11). (Munich, Germany). IEEE Computer Society, 2011.
[Wel00]
Nicholas Wells. “BusyBox: A Swiss Army Knife for Linux”. In: Linux Journal 10
(78es 2000).
[Whi+08]
Jules White, Douglas Schmidt, David Benavides, Pablo Trinidad, and Antonio
Ruiz-Cortés. “Automated Diagnosis of Product-Line Configuration Errors in
Feature Models”. In: Proceedings of the 12th Software Product Line Conference
(SPLC ’08). IEEE Computer Society, 2008, pages 225–234. DOI: 10.1109/
SPLC.2008.16.
[Whi+09]
Jules White, Brian Dougherty, Douglas C. Schmidt, and David Benavides. “Automated reasoning for multi-step feature model configuration problems”. In:
Proceedings of the 13th Software Product Line Conference (SPLC ’09). (San Francisco, CA, USA). Edited by Dirk Muthig and John D. McGregor. Carnegie Mellon
University, 2009, pages 11–20.
[Wik13a]
Wikipedia. Constraint Satisfaction Problem. 2013. URL: http://en.wikipedia.
org/wiki/Constraint_satisfaction_problem (visited on 03/17/2013).
[Wik13b]
Wikipedia. Satisfiability Modulo Theories. 2013. URL: http://en.wikipedia.
org/wiki/Satisfiability_Modulo_Theories (visited on 03/17/2013).
[ZHR]
Michal Zalewski, Niels Heinen, and Sebastian Roschke. skipfish. Web application
security scanner. URL: http://code.google.com/p/skipfish/ (visited
on 06/03/2012).
[ZK10]
Christoph Zengler and Wolfgang Küchlin. “Encoding the Linux Kernel Configuration in Propositional Logic”. In: Proceedings of the 19th European Conference
on Artificial Intelligence (ECAI 2010) Workshop on Configuration 2010. Edited
by Lothar Hotz and Alois Haselböck. 2010, pages 51–56.
[IDC12a]
IDC. Quarterly Server Market Report Q2. http://www.idc.com/getdoc.
jsp?containerId=prUS23513412. 2012.
[IDC12b]
IDC. Quarterly Smartphone Market Report Q3. http : / / www . idc . com /
getdoc.jsp?containerId=prUS23638712. 2012.
[ISO99]
ISO. ISO/IEC 9899:1999: Programming Languages — C. International Organization for Standardization, 1999.
162
Was this manual useful for you? yes no
Thank you for your participation!

* Your assessment is very important for improving the work of artificial intelligence, which forms the content of this project

Download PDF

advertisement