1# example of how to run this from XNU root, see README.md: 2# make -C tests/unit SDKROOT=macosx.internal example_test_osfmk 3 4PROJECT := xnu/unit_tests 5 6ifdef BASEDSTROOT 7override DSTROOT = $(BASEDSTROOT) 8endif 9 10DEVELOPER_DIR ?= $(shell xcode-select -p) 11INVALID_ARCHS = $(filter-out arm64%,$(ARCH_CONFIGS)) 12 13.DEFAULT_GOAL := install 14 15ORIG_SYMROOT := $(SYMROOT) 16 17ifdef OBJROOT 18XNU_OBJROOT := $(OBJROOT) 19else 20XNU_OBJROOT = $(XNU_ROOT)/BUILD/obj 21endif 22 23include $(DEVELOPER_DIR)/AppleInternal/Makefiles/darwintest/Makefile.common 24 25ifndef KERNEL_CONFIG 26KERNEL_CONFIG = development 27endif 28ifndef PRODUCT_CONFIG 29PRODUCT_CONFIG = j414c 30endif 31 32# find the name of the XNU library 33XNU_ROOT := $(abspath $(SRCROOT)/../..) 34KERNEL_CONFIG_UPPER := $(shell echo $(KERNEL_CONFIG) | tr '[:lower:]' '[:upper:]') 35XNU_DETAILS := $(shell $(SRCROOT)/tools/get_target_details.py $(SDKROOT) $(PRODUCT_CONFIG)) 36XNU_ARCH := $(word 1,$(XNU_DETAILS)) 37XNU_KERNEL_PLATFORM := $(word 2,$(XNU_DETAILS)) 38XNU_KERNEL_PLATFORM_UPPER := $(shell echo $(XNU_KERNEL_PLATFORM) | tr '[:lower:]' '[:upper:]') 39XNU_FILE_NAME_PREFIX := $(word 3,$(XNU_DETAILS)) 40 41XNU_BUILD_DIR := $(XNU_OBJROOT)/$(KERNEL_CONFIG_UPPER)_$(XNU_ARCH)_$(XNU_KERNEL_PLATFORM_UPPER) 42XNU_LIB_FILE_BASE := lib$(XNU_FILE_NAME_PREFIX).$(KERNEL_CONFIG).$(XNU_KERNEL_PLATFORM) 43XNU_LIB_FILE := $(XNU_BUILD_DIR)/$(XNU_LIB_FILE_BASE).a 44 45# avoid annoyances 46OTHER_CFLAGS += -Wno-missing-prototypes -Wno-unused-parameter -Wno-missing-variable-declarations 47# darwintest config 48DT_CFLAGS = -UT_NAMESPACE_PREFIX -DT_NAMESPACE_PREFIX=xnu -DT_LEAKS_DISABLE=1 -DBUILD_NO_STD_HEADERS -I$(DT_SYMLINKS_DIR) 49OTHER_CFLAGS += $(DT_CFLAGS) 50OTHER_CXXFLAGS += $(DT_CFLAGS) 51OTHER_LDFLAGS += -ldarwintest_utils 52# interpose header 53INTERPOSE_CFLAGS = -I$(SDKROOT)/usr/local/include/mach-o 54OTHER_CFLAGS += $(INTERPOSE_CFLAGS) 55# we build with clang but XNU contains C++ so we add this manually 56OTHER_LDFLAGS += -lc++ 57 58_v = $(if $(filter YES,$(or $(VERBOSE),$(RC_XBS))),,@) 59 60LD := "$(shell xcrun -sdk "$(SDKROOT)" -find ld)" 61DYLD_INFO := "$(shell xcrun -sdk "$(SDKROOT)" -find dyld_info)" 62LIBTOOL := "$(shell xcrun -sdk "$(SDKROOT)" -find libtool)" 63 64XNU_MAKE_FLAGS = BUILD_LTO=0 PRODUCT_CONFIGS=$(PRODUCT_CONFIG) KERNEL_CONFIGS=$(KERNEL_CONFIG) 65XNU_CFLAGS_EXTRA = 66COVERAGE_FLAGS = 67 68ifeq ($(BUILD_CODE_COVERAGE),1) 69# make XNU build coverage 70XNU_MAKE_FLAGS += BUILD_CODE_COVERAGE=1 71# make mocks code not mock some coverage related llvm functions 72COVERAGE_FLAGS += -D__BUILDING_FOR_COVERAGE__=1 73endif # BUILD_CODE_COVERAGE 74 75BUILD_SANITIZERS = 0 76SANITIZERS_FLAGS = 77ATTACH_SANITIZERS_SOURCES = 78SANCOV_FLAG = -fsanitize-coverage=bb,no-prune,trace-pc-guard 79 80ifeq ($(FIBERS_PREEMPTION),1) 81# trace-loads,trace-stores depends on either trace-pc-guard or libfuzzer instrumentation 82BUILD_SANCOV = 1 83# compile with memory operations instrumentation 84XNU_CFLAGS_EXTRA += -fsanitize-coverage=trace-loads,trace-stores 85# make mocks code aware of sanitizers runtime being linked 86SANITIZERS_FLAGS += -D__BUILDING_WITH_SANCOV_LOAD_STORES__=1 87endif # FIBERS_PREEMPTION 88 89ifeq ($(BUILD_ASAN),1) 90BUILD_SANITIZERS = 1 91# compile XNU with asan 92# TODO: enable globals instrumentation and write a proper ignorelist for problematic global vars 93XNU_CFLAGS_EXTRA += -fsanitize=address -mllvm -asan-globals=0 94# make mocks code aware of sanitizers runtime being linked 95SANITIZERS_FLAGS += -D__BUILDING_WITH_ASAN__=1 96endif # BUILD_ASAN 97 98ifeq ($(BUILD_UBSAN),1) 99BUILD_SANITIZERS = 1 100# compile XNU with ubsan 101# TODO: add more ubsan support 102XNU_CFLAGS_EXTRA += -fsanitize=signed-integer-overflow,shift,pointer-overflow,bounds,object-size,vla-bound,builtin 103# make mocks code aware of sanitizers runtime being linked 104SANITIZERS_FLAGS += -D__BUILDING_WITH_UBSAN__=1 105endif # BUILD_UBSAN 106 107ifeq ($(BUILD_TSAN),1) 108BUILD_SANITIZERS = 1 109# compile XNU with tsan 110XNU_CFLAGS_EXTRA += -fsanitize=thread 111# make mocks code aware of sanitizers runtime being linked 112SANITIZERS_FLAGS += -D__BUILDING_WITH_TSAN__=1 113endif # BUILD_TSAN 114 115# SanitizerCoverage is used for preemption simulation 116ifeq ($(BUILD_SANCOV),1) 117BUILD_SANITIZERS = 1 118# compile XNU with bb sancov 119XNU_CFLAGS_EXTRA += $(SANCOV_FLAG) -fsanitize-coverage-ignorelist=$(SRCROOT)/tools/sanitizers-ignorelist 120# make mocks code aware of sanitizers runtime being linked 121SANITIZERS_FLAGS += -D__BUILDING_WITH_SANCOV__=1 122endif # BUILD_SANCOV 123 124ifeq ($(BUILD_SANITIZERS),1) 125# include the ignorelist to disable instrumentation of some file/functions https://clang.llvm.org/docs/SanitizerSpecialCaseList.html 126XNU_CFLAGS_EXTRA += -fsanitize-ignorelist=$(SRCROOT)/tools/sanitizers-ignorelist 127# make mocks code aware of sanitizers runtime being linked 128SANITIZERS_FLAGS += -D__BUILDING_WITH_SANITIZER__=1 129ATTACH_SANITIZERS_SOURCES += mocks/san_attached.c 130endif # BUILD_SANITIZERS 131 132ifneq ($(strip $(XNU_CFLAGS_EXTRA)),) 133# add CFLAGS_EXTRA to the XNU make flags if any, wrap between "" as XNU_CFLAGS_EXTRA contains spaces and replace inner " with \" 134XNU_MAKE_FLAGS += CFLAGS_EXTRA="$(subst ",\",$(XNU_CFLAGS_EXTRA))" 135endif 136 137# sources for the mocks .dylib which overrides functions from XNU 138MOCK_SOURCES = mocks/mock_alloc.c \ 139 mocks/mock_misc.c \ 140 mocks/mock_pmap.c \ 141 mocks/mock_thread.c \ 142 mocks/mock_unimpl.c \ 143 mocks/mock_cpu.c \ 144 mocks/unit_test_utils.c \ 145 mocks/fibers/random.c \ 146 mocks/fibers/fibers.c \ 147 mocks/fibers/mutex.c \ 148 mocks/fibers/condition.c \ 149 mocks/fibers/rwlock.c \ 150 mocks/fibers/checker.c 151 152# sources for the mocks that are linked into the same .dylib as the XNU static lib 153# fake_kinit.c needs to be first because it contains the initialization entry point 154ATTACH_MOCK_SOURCES = mocks/fake_kinit.c \ 155 mocks/mock_3rd_party.c \ 156 mocks/mock_mem.c \ 157 mocks/mock_attached.c \ 158 $(ATTACH_SANITIZERS_SOURCES) 159 160# sources that are added to the compilation of every test target 161TEST_SIDE_SOURCES = mocks/dt_proxy.c 162 163# --------------------- individual tests customization ---------------------------- 164 165INCLUDED_TEST_SOURCE_DIRS += example_dir 166 167 168# --------------------------------------------------------------------------------- 169# we don't want to pass our arguments to the XNU makefile since that would confuse it, but we do want to pass any 170# -j argument if it existed 171unexport SRCROOT 172unexport SDKROOT 173unexport MAKEFLAGS 174MKJOBS = $(filter --jobs=%,$(MAKEFLAGS)) 175 176.FORCE: 177ifeq ($(SKIP_XNU),) 178# The XNU make needs to run every time since if anything changed in XNU, we want to rebuild the tests 179# This extra wait time can be skipped buy adding SKIP_XNU=1 to the make command line 180$(XNU_LIB_FILE): .FORCE 181 SDKROOT=$(SDKROOT) $(MAKE) -C $(XNU_ROOT) RC_ProjectName=xnu_libraries XNU_LibAllFiles=1 XNU_LibFlavour=UNITTEST SDKROOT=$(SDKROOT) $(XNU_MAKE_FLAGS) $(MKJOBS) 182endif # SKIP_XNU 183 184# Structure of unit-test linking: 185# Mocking of XNU functions relies on the interposable functions mechanism which requires the interposed and interposable 186# functions to be in different .dylibs 187# - tester (executable) 188# - compiles tester.c 189# - statically linked to libdarwintest.a 190# - statically linked to libside.a 191# - dynamically linked to libmocks.dylib 192# - dynamically linked to libkernel.xxx.dylib 193# - libside.a 194# - compiles mocks/dt_proxy.c ($(TEST_SIDE_SOURCES)) which bridges PT_xxx calls from libmocks and libkernel 195# to libdarwintest. This is needed since the test executable is the only mach-o which links to libdarwintest 196# - libmocks.dylib 197# - compiles mocks/mock_xxx.c - $(MOCK_SOURCES) files which contain T_MOCK() definitions to override functions from XNU 198# via the interposable functions mechanism. 199# - dynamically linked with libkernel.xxx.dylib 200# - libkernel.xxx.dylib 201# - compiles mocks/xxx.c - $(ATTACH_MOCK_SOURCES) files which contain dependencies needed for linking the XNU static library 202# - statically linked to libkernel.xxx.prelinked.a 203# - makes all functions interposable so that internal calls are routed to mocks 204# - libkernel.xxx.prelinked.a 205# - This is the same content as libkernel.xxx.a, passed through "ld -r" so that some symbols that are also 206# defined in libc can be unexported, so that anything outside XNU (i.e. mock and darwintest code) doesn't end up 207# calling them. e.g get_pid() 208# - libkernel.xxx.a 209# - This is the static lib produced by XNU make which contains all of the XNU code. 210 211# flags that .c files under MODULE (osfmk/bsd) are built with. MODULE should be defined per target 212MODULE_CFLAGS = $(shell $(SRCROOT)/tools/quote_defines.py $(XNU_BUILD_DIR)/$(MODULE)/$(KERNEL_CONFIG_UPPER)/.CFLAGS) -I$(XNU_BUILD_DIR)/$(MODULE)/$(KERNEL_CONFIG_UPPER) $(MODULE_FLAG) $(SANITIZERS_FLAGS) 213MODULE_CXXFLAGS = $(shell $(SRCROOT)/tools/quote_defines.py $(XNU_BUILD_DIR)/$(MODULE)/$(KERNEL_CONFIG_UPPER)/.CXXFLAGS) -I$(XNU_BUILD_DIR)/$(MODULE)/$(KERNEL_CONFIG_UPPER) $(MODULE_FLAG) $(SANITIZERS_FLAGS) 214OSFMK_CFLAGS = $(shell $(SRCROOT)/tools/quote_defines.py $(XNU_BUILD_DIR)/osfmk/$(KERNEL_CONFIG_UPPER)/.CFLAGS) -I$(XNU_BUILD_DIR)/osfmk/$(KERNEL_CONFIG_UPPER) 215MOCKS_CFLAGS = $(OSFMK_CFLAGS) $(INTERPOSE_CFLAGS) $(COVERAGE_FLAGS) $(SANITIZERS_FLAGS) 216 217# We need to link the darwintest headers from an empty folder and include from there since we can't add their 218# original folder as -I since that would allow XNU headers to include SDK headers. 219DT_SYMLINKS_DIR := $(OBJROOT)/darwintest_headers 220DT_ORIG_DIR := $(SDKROOT)/usr/local/include 221# Target to check if the sdk changed. content of the file is the path to the last sdk used for building 222$(OBJROOT)/sdk_updated: .FORCE 223 $(_v)if [ ! -f $(OBJROOT)/sdk_updated ] || [ "$$(cat $(OBJROOT)/sdk_updated)" != "$(SDKROOT)" ]; then \ 224 echo $(SDKROOT) > $(OBJROOT)/sdk_updated; \ 225 fi 226# Do we need to update the darwintest headers symlinks? 227$(DT_SYMLINKS_DIR): $(DT_ORIG_DIR)/darwintest.h $(OBJROOT)/sdk_updated 228 $(_v)mkdir -p $(DT_SYMLINKS_DIR) 229 $(_v)for file in $(DT_ORIG_DIR)/darwintest*; do ln -sfn "$${file}" $(DT_SYMLINKS_DIR); done 230 231# The LD invocation below can't get the arch and platform from the prelinked.a file so we need this empty object 232# to carry this information 233ARCH_DEF_OBJ := $(OBJROOT)/arch_def.o 234$(ARCH_DEF_OBJ): $(XNU_LIB_FILE) 235 $(_v)$(CC) -c -x c /dev/null -o $@ $(CFLAGS) 236 237SPTM_LIB := $(SDKROOT)/usr/local/lib/kernel/platform/libsptm_xnu.a 238# this creates a dummy executable with libsptm_xnu.a so that the xbs dependency analysis know to build xnu_unittests after libsptm was installed 239DUMMY_SPTM_EXE := $(OBJROOT)/ut_dummy_sptm 240$(DUMMY_SPTM_EXE): $(SPTM_LIB) $(OBJROOT)/sdk_updated $(ARCH_DEF_OBJ) 241 $(_v)echo "int start() { return 0; }" > $(OBJROOT)/dummy_sptm_start.c 242 $(_v)$(CC) $(CFLAGS) $(OBJROOT)/dummy_sptm_start.c -Wl,-e,_start -Wl,-pie -nostdlib -Wl,-kernel -static -Wl,-force_load,$(SPTM_LIB) -o $@ 243 244XNU_LIB_PRELINKED := $(OBJROOT)/$(XNU_LIB_FILE_BASE).prelinked.a 245$(XNU_LIB_PRELINKED): $(XNU_LIB_FILE) $(SRCROOT)/tools/xnu_lib.unexport $(ARCH_DEF_OBJ) 246 $(_v)$(LD) -r $(ARCH_DEF_OBJ) -all_load $(XNU_LIB_FILE) -o $@ -unexported_symbols_list $(SRCROOT)/tools/xnu_lib.unexport 247 248XNU_LIB_DYLIB := $(SYMROOT)/$(XNU_LIB_FILE_BASE).dylib 249$(XNU_LIB_DYLIB): $(XNU_LIB_PRELINKED) $(ATTACH_MOCK_SOURCES) $(DT_SYMLINKS_DIR) 250 $(_v)$(CC) $(MOCKS_CFLAGS) $(CFLAGS) -dynamiclib $(ATTACH_MOCK_SOURCES) -Wl,-all_load,$(XNU_LIB_PRELINKED) -lc++ -Wl,-undefined,dynamic_lookup -Wl,-interposable -install_name @rpath/$(XNU_LIB_FILE_BASE).dylib -o $@ 251 252# Do we need to update the unimplemented functions mock? 253$(OBJROOT)/func_unimpl.inc: $(XNU_LIB_DYLIB) 254 $(_v)echo "// Generated from undefined imports from: $(XNU_LIB_DYLIB)" > $@ 255 $(_v)$(DYLD_INFO) -imports $(XNU_LIB_DYLIB) | grep "flat-namespace" | awk '{print "UNIMPLEMENTED(" substr($$2, 2) ")"}' >> $@ 256# The xnu dylib is linked with -undefined dynamic_lookup so that it's able to find undefined symbols at load time 257# These are symbols that come from libsptm_xnu.a and libTightbeam.a. They still need to have an implementation for load 258# to succeeds so this gets the list of undefined symbols and defines them in the mocks dylib 259# use awk to discard the first underscore prefix of each symbol and wrap with UNIMPLEMENTED() macro expected in mock_unimpl.c 260 261MOCKS_DYLIB := $(SYMROOT)/libmocks.dylib 262$(MOCKS_DYLIB): $(MOCK_SOURCES) $(XNU_LIB_DYLIB) $(OBJROOT)/func_unimpl.inc 263 $(_v)$(CC) $(MOCKS_CFLAGS) $(CFLAGS) -I$(OBJROOT) $(XNU_LIB_DYLIB) -dynamiclib -MJ $@.json $(MOCK_SOURCES) $(XNU_LIB_DYLIB) -install_name @rpath/libmocks.dylib -o $@ 264# -install_name makes the test executables which link to these .dylibs find them in the same folder rather than the 265# folder the .dylibs happen to be built at. The path is relative to rpath and rpath is defined by the executable 266# itself to be to root of test executables, see below 267 268SIDE_LIB := $(OBJROOT)/libside.a 269TEST_SIDE_OBJ := $(foreach source,$(TEST_SIDE_SOURCES),$(OBJROOT)/$(notdir $(basename $(source))).o) 270$(TEST_SIDE_OBJ): $(TEST_SIDE_SOURCES) $(DT_SYMLINKS_DIR) $(XNU_LIB_DYLIB) 271 $(_v)$(CC) $(MOCKS_CFLAGS) $(CFLAGS) $(DT_CFLAGS) -c $< -o $@ 272$(SIDE_LIB): $(TEST_SIDE_OBJ) 273 $(_v)$(LIBTOOL) -static $< -o $@ 274 275# this creates a shell script for running all the unit-tests on-desk (build all using target 'install' 276.PHONY: run_unittests 277install: run_unittests 278RUN_UT_SCRIPT := $(SYMROOT)/run_unittests.sh 279RUN_UT_LIST := $(SYMROOT)/run_unittests.targets 280run_unittests: $(RUN_UT_SCRIPT) 281$(RUN_UT_SCRIPT): $(SRCROOT)/tools/make_run_unittests.py 282 $(SRCROOT)/tools/make_run_unittests.py "$(TEST_TARGETS)" $@ $(RUN_UT_LIST) 283 chmod a+x $@ 284 285# inform the dt makefile that these need to be installed along with the test executables 286CUSTOM_TARGETS += $(XNU_LIB_DYLIB) $(MOCKS_DYLIB) $(DUMMY_SPTM_EXE) $(RUN_UT_SCRIPT) 287install-$(XNU_LIB_DYLIB): $(XNU_LIB_DYLIB) 288 mkdir -p $(INSTALLDIR) 289 cp $< $(INSTALLDIR)/ 290install-$(MOCKS_DYLIB): $(MOCKS_DYLIB) 291 mkdir -p $(INSTALLDIR) 292 cp $< $(INSTALLDIR)/ 293install-$(DUMMY_SPTM_EXE): $(DUMMY_SPTM_EXE) 294 echo "not copying $(DUMMY_SPTM_EXE)" 295install-$(RUN_UT_SCRIPT): $(RUN_UT_SCRIPT) 296 mkdir -p $(INSTALLDIR) 297 cp $< $(INSTALLDIR)/ 298 cp $(RUN_UT_LIST) $(INSTALLDIR)/ 299 300OTHER_LDFLAGS += $(XNU_LIB_DYLIB) $(MOCKS_DYLIB) -Wl,-force_load,$(SIDE_LIB) 301 302 303include $(DEVELOPER_DIR)/AppleInternal/Makefiles/darwintest/Makefile.targets 304 305# Every unit-test target needs to define a target-specific variable named UT_MODULE so that MODULE_CFLAGS is defined 306# This is parsed from the .c file that needs to have a line like: #define UT_MODULE osfmk 307# The clang invocation may produce errors due to missing include folders but it still generates the #defines list. 308define assign_module 309$(1): MODULE ?= $(shell for f in $(1).c $(1).cpp; do \ 310 [ -f $$f ] && $(CC) -E -dM $$f 2>/dev/null | grep -m1 '^#define UT_MODULE ' | awk '{print $$3}' && break; \ 311done) 312endef 313$(foreach target, $(TEST_TARGETS), $(eval $(call assign_module, $(target), $(target)))) 314# if no UT_MODULE was found, this will trigger an error in std_safe.h 315MODULE_FLAG = -DUT_MODULE=$(if $(strip $(MODULE)),$(MODULE),-1) 316 317# All test targets depend on the XNU lib 318$(TEST_TARGETS): $(XNU_LIB_DYLIB) $(MOCKS_DYLIB) $(SIDE_LIB) 319 320# if the test executalbe is in a sub-folder, it's rpath needs to point back to the root of all 321# test executables. This is done by appending as many ".." to @executable_path as there are levels of sub-directories 322define make_rpath 323@executable_path$(shell \ 324 dir=$$(dirname "$1"); \ 325 if [ "$$dir" != "." ]; then \ 326 echo "$$dir" | sed 's|[^/][^/]*|..|g' | sed 's|^|/|'; \ 327 fi) 328endef 329# this sets the CFLAGS for building the test to be the same as files in it's UT_MODULE 330$(TEST_TARGETS): OTHER_CFLAGS += $(MODULE_CFLAGS) -MJ $(OBJROOT)/$(subst /,__,$@).json -rpath $(call make_rpath,$@) 331# C++ files get both CFLAGS and CXXFLAGS 332$(TEST_TARGETS): OTHER_CXXFLAGS += $(MODULE_CXXFLAGS) $(MODULE_CFLAGS) -MJ $(OBJROOT)/$(subst /,__,$@).json -rpath $(call make_rpath,$@) 333 334# This is for creating a new version of $(XNU_ROOT)/compile_commands.json that includes 335# the test and mock files. It's used by IDEs for understanding the code 336.PHONY: cmds_json proj_xcode proj_vscode proj_clion 337cmds_json: 338 $(SRCROOT)/tools/merge_cmds_json.py $(XNU_ROOT) $(XNU_BUILD_DIR) $(OBJROOT) 339 340proj_xcode: cmds_json 341 $(SRCROOT)/tools/generate_ut_proj.py xcode 342proj_vscode: cmds_json 343 $(SRCROOT)/tools/generate_ut_proj.py vscode 344proj_clion: cmds_json 345 $(SRCROOT)/tools/generate_ut_proj.py clion 346 347