-
-
Notifications
You must be signed in to change notification settings - Fork 606
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This patch introduces another new build mechanism that allows creating custom kernel exporting only symbols required by specific application. Such kernel benefits from smaller size and better security as all unneeded code is removed. This patch addresses remaining part of the modularization/librarization functionality as explained by the issue #1110 and this part of the roadmap - https://github.com/cloudius-systems/osv/wiki/Roadmap#modularizationlibrarization. This idea was also mentioned in the P99 OSv presentation - see slide 12. In essence, this patch adds two new scripts that analyse the build manifest, detect ELF files and identify symbols required from OSv kernel and finally produce an application specific version script under build/last/app_version_script: - scripts/list_manifest_files.py - reads build/last/usr.manifest and produces a list of file paths on host filesystem - scripts/generate_app_version_script.sh - iterates over manifest files produced by list_manifest_files.py, identifies undefined symbols in the ELF files using objdump that are also exported by OSv kernel and finally generates build/last/app_version_script This patch also makes some modest changes to the main makefile to support new parameter - conf_version_script - intended to point to a custom version script. Please note that this new functionality only works when building kernel with most symbols hidden (conf_hide_symbols=1). To take advantage of this new feature one would follow these steps: 1. Build image for given application. 2. Run scripts/generate_app_version_script.sh to produce app_version_script. 3. Re-build the image with kernel exporting only symbols needed by an app like so: ./scripts/build fs=rofs conf_hide_symbols=1 image=golang-pie-example \ conf_version_script=build/last/app_version_script The version script generated for the golang ELF list only 30 symbols. My experiments show that for many apps this can reduce kernel size by close to 0.5MB. For example the size of kernel taylored to the golang app above is 3196K vs 3632K of the generic ones. Obviously this feature can be used together with the driver profile to further reduce kernel size. The kernel produced with the build command below is only 2688K in size: ./scripts/build fs=rofs conf_hide_symbols=1 image=golang-pie-example \ drivers_profile=virtio-mmio conf_version_script=build/last/app_version_script Please note that some application use dlsym() to dynamically resolve symbols which would be missed by this technique. In such scenarios such symbols would have to be manually added to app_version_script. Fixes #1110 Signed-off-by: Waldemar Kozaczuk <[email protected]>
- Loading branch information
Showing
4 changed files
with
160 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
#!/bin/bash | ||
|
||
if [[ "$1" == "--help" || "$1" == "-h" ]]; then | ||
cat <<-EOF | ||
Produce version script file under build/last/app_version_script intended | ||
to build custom kernel exporting only symbols listed in this file. | ||
The script reads default user manifest file - build/last/usr.manifest | ||
to identify all ELF files - executables and shared libraries - and | ||
extract names of all symbols required to be exported by OSv kernel. | ||
You can override location of the source manifest and pass its path | ||
as 1st argument. | ||
Usage: ${0} [<manifest_file_path>] | ||
NOTE: Given that some executables and libraries may dynamically resolve | ||
symbols using dlsym(), this script would miss to identify those. In this | ||
case one would have to manually add those symbols to build/last/app_version_script. | ||
EOF | ||
exit 0 | ||
fi | ||
|
||
MACHINE=$(uname -m) | ||
if [ "${MACHINE}" == "x86_64" ]; then | ||
ARCH="x64" | ||
else | ||
ARCH="aarch64" | ||
fi | ||
|
||
VERSION_SCRIPT_START=$(cat <<"EOF" | ||
{ | ||
global: | ||
EOF | ||
) | ||
|
||
VERSION_SCRIPT_END=$(cat <<"EOF" | ||
local: | ||
*; | ||
}; | ||
EOF | ||
) | ||
|
||
BUILD_DIR=$(dirname $0)/../build/last | ||
VERSION_SCRIPT_FILE=$(dirname $0)/../build/last/app_version_script | ||
|
||
ALL_SYMBOLS_FILE=$BUILD_DIR/all.symbols | ||
if [[ ! -f $ALL_SYMBOLS_FILE ]]; then | ||
echo "Could not find $ALL_SYMBOLS_FILE. Please run build first!" | ||
exit 1 | ||
fi | ||
|
||
USR_MANIFEST=$1 | ||
if [[ "$USR_MANIFEST" == "" ]]; then | ||
USR_MANIFEST=$BUILD_DIR/usr.manifest | ||
fi | ||
if [[ ! -f $USR_MANIFEST ]]; then | ||
echo "Could not find $USR_MANIFEST. Please run build first!" | ||
exit 1 | ||
fi | ||
|
||
MANIFEST_FILES=$BUILD_DIR/usr.manifest.files | ||
echo "Extracting list of files on host from $USR_MANIFEST" | ||
scripts/list_manifest_files.py > $MANIFEST_FILES | ||
|
||
extract_symbols_from_elf() | ||
{ | ||
local ELF_PATH=$1 | ||
echo "/*------- $ELF_PATH */" | ||
objdump -wT ${ELF_PATH} | grep UND | cut -c 62- | \ | ||
sort -d | uniq | comm - ${ALL_SYMBOLS_FILE} -12 | \ | ||
awk '// { printf(" %s;\n", $0) }' | tee /tmp/generate_app_version_script_symbols | ||
if [[ $(grep dlsym /tmp/generate_app_version_script_symbols) != "" ]]; then | ||
echo "WARNING: the $ELF_PATH may use dlsym() to dynamically reference symbols!" 1>&2 | ||
fi | ||
} | ||
|
||
echo "Writing to $VERSION_SCRIPT_FILE ..." | ||
echo "$VERSION_SCRIPT_START" > $VERSION_SCRIPT_FILE | ||
|
||
cat $MANIFEST_FILES | xargs file | grep "ELF 64-bit" | cut --delimiter=: -f 1 | \ | ||
while read file; do extract_symbols_from_elf "$file"; done >> $VERSION_SCRIPT_FILE | ||
|
||
echo "$VERSION_SCRIPT_END" >> $VERSION_SCRIPT_FILE |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
#!/usr/bin/python3 | ||
|
||
import optparse, os, sys, subprocess | ||
from manifest_common import add_var, expand, unsymlink, read_manifest, defines | ||
|
||
def list_files(manifest,manifest_dir): | ||
manifest = [(x, y % defines) for (x, y) in manifest] | ||
files = list(expand(manifest)) | ||
files = [(x, unsymlink(y)) for (x, y) in files] | ||
|
||
for name, hostname in files: | ||
if not hostname.startswith("->"): | ||
if os.path.islink(hostname): | ||
link = os.readlink(hostname) | ||
print(link) | ||
elif not os.path.isdir(hostname): | ||
if not os.path.isabs(hostname): | ||
hostname = os.path.join(manifest_dir,hostname) | ||
print(hostname) | ||
|
||
def main(): | ||
make_option = optparse.make_option | ||
|
||
opt = optparse.OptionParser(option_list=[ | ||
make_option('-m', | ||
dest='manifest', | ||
help='read manifest from FILE', | ||
metavar='FILE'), | ||
make_option('-D', | ||
type='string', | ||
help='define VAR=DATA', | ||
metavar='VAR=DATA', | ||
action='callback', | ||
callback=add_var) | ||
]) | ||
|
||
(options, args) = opt.parse_args() | ||
|
||
if not 'libgcc_s_dir' in defines: | ||
libgcc_s_path = subprocess.check_output(['gcc', '-print-file-name=libgcc_s.so.1']).decode('utf-8') | ||
defines['libgcc_s_dir'] = os.path.dirname(libgcc_s_path) | ||
|
||
manifest_path = options.manifest or 'build/last/usr.manifest' | ||
manifest_dir = os.path.abspath(os.path.dirname(manifest_path)) | ||
|
||
manifest = read_manifest(manifest_path) | ||
list_files(manifest,manifest_dir) | ||
|
||
if __name__ == "__main__": | ||
main() |