xref: /xnu-12377.61.12/tests/unit/Makefile (revision 4d495c6e23c53686cf65f45067f79024cf5dcee8)
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