Home · ASPLOS 2024 · Operating Systems & Virtualization

Loupe: Driving the Development of OS Compatibility Layers

Hugo Lefeuvre, Gaulthier Gain, Vlad-Andrei Badoiu, Daniel Dinca, Vlad-Radu Schiller, Costin Raiciu and 2 others

Conference
ASPLOS 2024
Published
2024
Pages
249-267
Citations
5

A new operating system is useless if it can't run existing software, so its developers must build a compatibility layer that implements the hundreds of Linux system calls apps expect — a huge, months-long slog, and existing tools wildly overestimate how much is actually needed by counting every call an app could ever make; this work from Manchester and collaborators builds Loupe, which runs each app and empirically finds which system calls truly need implementing versus which can be faked or stubbed away, cutting the work by more than half and handing developers a step-by-step plan.

The argument in one glance

  1. Problem

    A new OS needs to run existing apps, which means huge porting work

    An operating system is only as useful as the software it can run. To gain traction, a new OS must run unmodified applications written for Linux, which means building a compatibility layer that reimplements the Linux system-call interface. For each application, developers must identify the OS features it needs and then implement them, repeating this app by app — a large, well-known engineering burden that today is done in an ad-hoc, organic way with no systematic sense of what to build or in what order.

  2. What people did

    Estimate the needed features with static or dynamic analysis

    To figure out which OS features an application needs, developers reach for analysis tools. Static analysis inspects the application's source or binary to enumerate every system call it might invoke. Naive dynamic analysis, using a tool like strace, runs the application and traces the system calls it actually makes during that run.

  3. Why it fell short

    Both wildly overestimate what actually needs implementing

    Static analysis is comprehensive but conservative: it counts every call reachable under any configuration or error path, including dead and rarely-used code, overestimating by two to five times. Naive tracing is closer but still overestimates, because it ignores the practices early OS developers actually use — stubbing (return not-implemented) and faking (return success) system calls without implementing them. Many applications tolerate this, so a large fraction of the calls both methods report do not actually need an implementation to run real workloads.

  4. This paper's idea

    Measure empirically which calls truly need implementing

    Loupe is a dynamic-analysis tool that runs each application under its own test workload and, for every system call it invokes, tests in separate runs whether stubbing or faking that call still lets the workload pass — so the truly-required calls are exactly those that neither works for. Guarded by resource and performance checks, Loupe measures the real minimal set of features, then turns the results into a prioritized development plan telling an OS which calls to implement first to support the most applications the soonest.

Why this problem is worth solving

To see why the burden is real, and where the waste is, it helps to understand what a compatibility layer is.

An operating system is only useful if it can run software people want, and almost all that software is written for a mainstream OS — for Linux, concretely, against the Linux system-call interface (the roughly four hundred calls like read, write, mmap, and futex through which programs ask the kernel for services), plus special pseudo-files like /dev/random and /proc. A brand-new research or prototype OS has none of that, so to run an existing application unmodified it must build a compatibility layer: code that presents the same interface the application expects and services its requests. This matters enormously for a young OS — running real applications is how it gathers performance numbers, attracts contributors and investors, and eventually reaches real use. But building the layer is a substantial, well-documented engineering effort. It proceeds application by application: pick an app, determine which OS features it requires, implement them, then move to the next app. Manually porting each application instead does not scale. And the whole process is typically done ad-hoc — there is no systematic way to decide which features to build, in what order, which applications to target, or how completely each feature must be implemented. That lack of method is what wastes engineering effort, and it is the identifying-what-to-build half of the problem that this work sets out to systematize.

The specific reason existing tools mislead is that they ignore how OS developers actually work in the early days.

In the early stages of building an OS, the goal is not to fully support every feature an application might touch — it is to get the application’s core functionality running as quickly as possible. Developers achieve this with two tricks that existing analysis tools do not model. The first is stubbing: when the application invokes a system call the OS has not implemented, simply return an error code meaning “not implemented” instead of doing the work. The second is faking: return a success code without doing the work. Both leave the feature unimplemented, and the striking fact is that a great many applications keep running correctly anyway, because programs are written to tolerate system calls failing or behaving oddly. A third practice, partial implementation, handles only the parts of a complex call that a workload actually uses. These practices are essential — without them, showcasing a use-case on a new OS would take far longer or be infeasible for a small team. Yet static analysis and naive dynamic tracing both count every invoked call as required, blind to the fact that half of them or more can be stubbed or faked. So developers are left guessing, through trial and error, which calls they truly need and which they can skip — the very guesswork Loupe replaces with measurement.

What people did before, and where it broke

  • Static analysis (source or binary). Inspect the application code to enumerate every system call it could ever invoke. Where it broke. It is comprehensive but conservative, counting calls reachable only under rare configurations, error paths, and dead code — overestimating the requirement by two to five times, an upper bound of limited use to a young OS. Binary analysis is also imprecise, and source analysis is language-specific and hard to scale.
  • Naive dynamic tracing (strace). Run the application and record the system calls it actually makes. Where it broke. It ignores stubbing and faking, so it reports as required many calls that applications tolerate failing — roughly half of them can be skipped for real workloads — still overestimating the true implementation effort.
  • Ad-hoc trial and error. Fall back to manually guessing which calls to implement first and which to stub or fake. Where it broke. It is unsystematic and error-prone, prone to wasting effort on unneeded features and to stubbing or faking something that silently breaks stability or skews performance comparisons.
  • The gap. No tool measured the real minimal set of features an application needs — accounting for stubbing, faking, and partial implementation — and turned that into a prioritized plan for building a compatibility layer. Doing exactly that, by dynamic analysis under real workloads, is what Loupe provides.

How Loupe works

From a conservative over-estimate to a measured, prioritized plan

  1. new

  2. new

  3. new

  4. new

  5. new

The idea

Measure what the workload actually requires instead of assuming the worst case. Rather than count every call the code could reach, Loupe runs the application against a real test workload and observes what it genuinely needs, treating the workload itself as the definition of the features that matter — an empirical minimum, not a conservative upper bound. Replacing a pessimistic static estimate with a measurement of what is really needed, and trusting the observed behavior over the theoretical possibility, is the same instinct the hyperdimensional early-termination work covered elsewhere on this site follows, stopping the moment the evidence shows enough has been done rather than always paying for the full, worst-case computation.

Implement a feature only when it is truly forced — otherwise stub or fake it. The heart of Loupe is deciding, per call, whether the application can survive that call being left unimplemented, so that engineering effort is spent only on the calls nothing else will satisfy. Deferring the costly work until the moment it is genuinely required, and getting away with a cheap placeholder everywhere else, is exactly the discipline the lazy-barrier storage work covered elsewhere on this site embodies, issuing its expensive ordering operation only when correctness truly demands it and skipping it the rest of the time.

Concentrate effort on the small critical subset that carries the weight. Loupe reveals that of all the calls an application appears to need, only a minority — the fundamental ones like process creation, socket setup, and memory mapping — genuinely require implementation, and its plan puts those first. Recognizing that a small, identifiable subset accounts for what really matters while the bulk can be handled cheaply, and prioritizing accordingly, is the same strategy the low-bit language-model serving work covered elsewhere on this site uses, spending its expensive machinery on the small set of critical values and running the common remainder on the cheap path.

Following an application through Loupe

Measuring what a web server really needs

Tracing one analysis shows stubbing and faking tested call by call.

Step 1 of 5

Provide the app and a test script

A developer wants their new OS to run a web server. They give Loupe the server binary and a short test script that sends a request and checks that the server responds correctly — the workload that defines what must work.

Loupe operates on binaries, so there are no language or compiler restrictions.

Why it works

  • Static analysis counts the impossible. It enumerates every call the code could ever reach → so it includes rare configurations and dead error paths no real workload hits → so it overstates the requirement by two to five times.
  • Naive tracing counts the skippable. It records every call a run makes → but many of those calls can be stubbed or faked without breaking the application → so it still overstates, missing that around half can be skipped.
  • The workload defines what matters. In early development the target is core functionality exercised by benchmarks and test suites → so the chosen workload itself pins down which features are needed → so dynamic analysis measures the real minimal set rather than a conservative ceiling.
  • Applications tolerate failure. Programs detect a failed call and ignore it, use an alternative call, or fall back to a safe default → so stubbing or faking a non-critical call leaves the application running → so those calls need no implementation to support the workload.
  • Ranking enables prioritization. The measurements show how many applications need each call → so the plan implements the highest-value calls first → so the OS supports the most applications with the least engineering, and as early as possible.

What it costs (trade-offs and scope)

  • It only finds what the workload exercises. Dynamic analysis measures the features a given test workload triggers, so a thin workload misses features real users would need, and comprehensive production support eventually still requires everything static analysis conservatively lists.
  • Stubbing and faking are workload-scoped promises. A call safely faked for a benchmark may break a feature the benchmark never exercised, so the guarantees hold only for the tested behavior, and faking the wrong thing can silently corrupt results or skew performance comparisons.
  • It streamlines identifying features, not implementing them. Loupe tells you which calls to implement and in what order, but writing each implementation is OS-specific work it does not reduce.
  • Analysis has a real runtime cost. A full analysis re-runs the workload many times — minutes for a health check, but hours to over a day for a large test suite — though a shared results database amortizes this across projects.
  • It needs a test script per application. Each application requires a short script that supplies input and judges success, a modest but real per-application effort.
  • The savings estimates rest on approximations. Comparing against an OS’s organic development order is reconstructed from repository history, and system calls differ in implementation cost, so the effort-reduction figures are solid estimates rather than exact measurements.

The evaluation

Three ways to estimate the OS features an application needs

PropertyStatic analysisNaive dynamic (strace)Loupe
What it countsEvery call a run makes
Accounts for stubbing and fakingNo
Over-estimationLarge (about half skippable)
OutputA conservative listAll traced calls
Three ways to estimate the OS features an application needs, evaluated across 116 applications and eleven prototype OSes. Loupe measures the true minimal set by testing stubbing and faking per call: on average 46 percent of a test suite's system calls (and 60 percent of a benchmark's) can be skipped, so to support half of the 62 applications an OS supported, Loupe needs only 37 system calls implemented versus 92 for the organic ad-hoc process and 142 for naive dynamic analysis — and it emits a prioritized plan whose steps mostly add just one to three calls each. Static and naive analysis overestimate by two to five times.Underlined values explain themselves when clicked.

Check yourself

Check your understanding

  1. 1. Why must a new operating system build a compatibility layer, and why is it costly?

  2. 2. What are "stubbing" and "faking," and why do existing analysis tools mislead by ignoring them?

  3. 3. How does Loupe measure the true minimal set of system calls an application needs?

  4. 4. Which statement best reflects Loupe's results and scope?

Sources

The paper is “Loupe: Driving the Development of OS Compatibility Layers” by Hugo Lefeuvre, Gaulthier Gain, Vlad-Andrei Badoiu, Daniel Dinca, Vlad-Radu Schiller, Costin Raiciu, Felipe Huici, and Pierre Olivier, published at ASPLOS 2024, DOI 10.1145/3617232.3624861. The authors are at the University of Manchester, the University of Liege, University Politehnica of Bucharest, and Unikraft.io; Loupe is open-source and used in the Unikraft OS.

This explainer is written from the full text of the paper (the authors’ arXiv version, 2309.15996, matching the ASPLOS proceedings). Everything above comes from the paper’s own sections: the introduction and motivation framing compatibility layers and the two-step identify-and-implement process; the limitations of static analysis (its conservative over-estimation, the Tsai et al. figure of 272 system calls to support an entire Ubuntu distribution, and the Redis example of over 100 statically-identified calls versus 42 for the test suite and 20 for the benchmark) and of naive dynamic analysis (its failure to model stubbing, faking, and partial implementation, with the HermiTux sigaltstack-stub and mprotect-fake code examples); the discussion of when to stub or fake and the stability and performance concerns; the Loupe design (its seccomp/ptrace-based per-call stub-and-fake testing, containerized replicas, success and performance evaluation, vectored-system-call and pseudo-file handling, test-suite whitelisting, Debhelper integration, the runtime formula, and the shared database); the OS-feature support guide with the step-by-step plans for eleven OSes and the OSv engineering-savings study (37 versus 92 versus 142 system calls); and the Linux-API analysis (Loupe reporting 148 system calls versus 180 for naive analysis to support all 116 applications, the two-to-five-times static overestimation, the 46-percent test-suite and 60-percent benchmark stub/fake rates, the reasons applications tolerate failure, and the return-value-check study).

Related papers on Operating Systems & Virtualization

See all 170 →