Loading...
  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
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
# example of how to run this from XNU root, see README.md:
#   make -C tests/unit SDKROOT=macosx.internal example_test_osfmk

PROJECT := xnu/unit_tests

ifdef BASEDSTROOT
override DSTROOT = $(BASEDSTROOT)
endif

DEVELOPER_DIR ?= $(shell xcode-select -p)
TOOLCHAIN_DIR := $(shell xcrun --sdk $(SDKROOT) --show-toolchain-path)
INVALID_ARCHS = $(filter-out arm64%,$(ARCH_CONFIGS))

.DEFAULT_GOAL := install

ORIG_SYMROOT := $(SYMROOT)

ifdef OBJROOT
XNU_OBJROOT := $(OBJROOT)
else
XNU_OBJROOT = $(XNU_ROOT)/BUILD/obj
endif

include $(DEVELOPER_DIR)/AppleInternal/Makefiles/darwintest/Makefile.common

ifndef KERNEL_CONFIG
KERNEL_CONFIG = development
endif
ifndef PRODUCT_CONFIG
PRODUCT_CONFIG = j414c
endif

# find the name of the XNU library
# use make ... print-XXX to show any variable
XNU_ROOT := $(abspath $(SRCROOT)/../..)
KERNEL_CONFIG_UPPER := $(shell echo $(KERNEL_CONFIG) | tr '[:lower:]' '[:upper:]')
XNU_DETAILS := $(shell $(SRCROOT)/tools/get_target_details.py $(SDKROOT) $(PRODUCT_CONFIG))
XNU_ARCH := $(word 1,$(XNU_DETAILS))
XNU_KERNEL_PLATFORM := $(word 2,$(XNU_DETAILS))
XNU_KERNEL_PLATFORM_UPPER := $(shell echo $(XNU_KERNEL_PLATFORM) | tr '[:lower:]' '[:upper:]')
# in XNU makefile, KERNEL_FILE_NAME_PREFIX depends on the SDK platform, which is always MacOSX here
XNU_FILE_NAME_PREFIX := kernel
XNU_PLATFORM_FOR_CONFIG := $(word 3,$(XNU_DETAILS))

XNU_BUILD_DIR := $(XNU_OBJROOT)/$(KERNEL_CONFIG_UPPER)_$(XNU_ARCH)_$(XNU_KERNEL_PLATFORM_UPPER)
XNU_FILE_BASE := $(XNU_FILE_NAME_PREFIX).$(KERNEL_CONFIG).$(XNU_KERNEL_PLATFORM)
XNU_LIB_FILE_BASE := lib$(XNU_FILE_BASE)
XNU_LIB_FILE := $(XNU_BUILD_DIR)/$(XNU_LIB_FILE_BASE).a
XNU_LIB_DSYM := $(XNU_BUILD_DIR)/$(XNU_FILE_BASE).dSYM
XNU_ALIAS_LIST := $(XNU_BUILD_DIR)/all-alias.exp

# add Werror for consistency with the kernel
USE_WERROR := 1
ifneq ($(BUILD_WERROR),)
USE_WERROR := $(BUILD_WERROR)
endif
ifeq ($(USE_WERROR),1)
WERROR := -Werror
endif
OTHER_CFLAGS += $(WERROR)

# avoid annoyances
OTHER_CFLAGS += -Wno-missing-prototypes -Wno-unused-parameter -Wno-missing-variable-declarations
# darwintest config
DT_CFLAGS = -UT_NAMESPACE_PREFIX -DT_NAMESPACE_PREFIX=xnu -DT_LEAKS_DISABLE=1 -DBUILD_NO_STD_HEADERS -I$(DT_SYMLINKS_DIR)
OTHER_CFLAGS += $(DT_CFLAGS)
OTHER_CXXFLAGS += $(DT_CFLAGS)
OTHER_LDFLAGS += -ldarwintest_utils
# interpose header
INTERPOSE_CFLAGS = -I$(SDKROOT)/usr/local/include/mach-o
OTHER_CFLAGS += $(INTERPOSE_CFLAGS)
OTHER_CXXFLAGS += $(INTERPOSE_CFLAGS)
# we build with clang but XNU contains C++ so we add this manually
OTHER_LDFLAGS += -lc++

_v = $(if $(filter YES,$(or $(VERBOSE),$(RC_XBS))),,@)

LD := "$(shell xcrun -sdk "$(SDKROOT)" -find ld)"
DYLD_INFO := "$(shell xcrun -sdk "$(SDKROOT)" -find dyld_info)"
LIBTOOL := "$(shell xcrun -sdk "$(SDKROOT)" -find libtool)"
STRIP := "$(shell xcrun -sdk "$(SDKROOT)" -find strip)"
DSYMUTIL := "$(shell xcrun -sdk "$(SDKROOT)" -find dsymutil)"
NM := "$(shell xcrun -sdk "$(SDKROOT)" -find nm)"

# BUILD_LTO is disabled to avoid optimizations that would hinder interposing
XNU_MAKE_FLAGS := BUILD_LTO=0 PRODUCT_CONFIGS=$(PRODUCT_CONFIG) KERNEL_CONFIGS=$(KERNEL_CONFIG) PLATFORM_FOR_CONFIG=$(XNU_PLATFORM_FOR_CONFIG)
XNU_CFLAGS_EXTRA =
COVERAGE_FLAGS =

ifeq ($(BUILD_CODE_COVERAGE),1)
# make XNU build coverage
XNU_MAKE_FLAGS += BUILD_CODE_COVERAGE=1
# make mocks code not mock some coverage related llvm functions
COVERAGE_FLAGS += -D__BUILDING_FOR_COVERAGE__=1
endif # BUILD_CODE_COVERAGE

BUILD_SANITIZERS = 0
SANITIZERS_FLAGS =
ATTACH_SANITIZERS_SOURCES =
SANCOV_FLAG = -fsanitize-coverage=bb,no-prune,trace-pc-guard

ifeq ($(FIBERS_PREEMPTION),1)
# trace-loads,trace-stores depends on either trace-pc-guard or libfuzzer instrumentation
BUILD_SANCOV = 1
# compile with memory operations instrumentation
XNU_CFLAGS_EXTRA += -fsanitize-coverage=trace-loads,trace-stores
# make mocks code aware of sanitizers runtime being linked
SANITIZERS_FLAGS += -D__BUILDING_WITH_SANCOV_LOAD_STORES__=1
endif # FIBERS_PREEMPTION

ifeq ($(BUILD_LIBFUZZER),1)
BUILD_SANCOV = 1
# compile XNU with libfuzzer, redefine the sancov flag to use libfuzzer coverage collection
SANCOV_FLAG = -fsanitize=fuzzer-no-link
# make mocks code aware of sanitizers runtime being linked
SANITIZERS_FLAGS += -D__BUILDING_WITH_LIBFUZZER__=1
endif # BUILD_LIBFUZZER

ifeq ($(BUILD_ASAN),1)
BUILD_SANITIZERS = 1
# compile XNU with asan
# TODO: enable globals instrumentation and write a proper ignorelist for problematic global vars
XNU_CFLAGS_EXTRA += -fsanitize=address -mllvm -asan-globals=0
# make mocks code aware of sanitizers runtime being linked
SANITIZERS_FLAGS += -D__BUILDING_WITH_ASAN__=1
endif # BUILD_ASAN

ifeq ($(BUILD_UBSAN),1)
BUILD_SANITIZERS = 1
# compile XNU with ubsan
# TODO: add more ubsan support
XNU_CFLAGS_EXTRA += -fsanitize=signed-integer-overflow,shift,pointer-overflow,bounds,object-size,vla-bound,builtin
# make mocks code aware of sanitizers runtime being linked
SANITIZERS_FLAGS += -D__BUILDING_WITH_UBSAN__=1
endif # BUILD_UBSAN

ifeq ($(BUILD_TSAN),1)
BUILD_SANITIZERS = 1
# compile XNU with tsan
XNU_CFLAGS_EXTRA += -fsanitize=thread
# make mocks code aware of sanitizers runtime being linked
SANITIZERS_FLAGS += -D__BUILDING_WITH_TSAN__=1
endif # BUILD_TSAN

# SanitizerCoverage is used for preemption simulation
ifeq ($(BUILD_SANCOV),1)
BUILD_SANITIZERS = 1
# compile XNU with bb sancov
XNU_CFLAGS_EXTRA += $(SANCOV_FLAG) -fsanitize-coverage-ignorelist=$(SRCROOT)/tools/sanitizers-ignorelist
# make mocks code aware of sanitizers runtime being linked
SANITIZERS_FLAGS += -D__BUILDING_WITH_SANCOV__=1
endif # BUILD_SANCOV

ifeq ($(BUILD_SANITIZERS),1)
# include the ignorelist to disable instrumentation of some file/functions https://clang.llvm.org/docs/SanitizerSpecialCaseList.html
XNU_CFLAGS_EXTRA += -fsanitize-ignorelist=$(SRCROOT)/tools/sanitizers-ignorelist
# make mocks code aware of sanitizers runtime being linked
SANITIZERS_FLAGS += -D__BUILDING_WITH_SANITIZER__=1
ATTACH_SANITIZERS_SOURCES += mocks/san_attached.c
endif # BUILD_SANITIZERS

ifneq ($(strip $(XNU_CFLAGS_EXTRA)),)
# add CFLAGS_EXTRA to the XNU make flags if any, wrap between "" as XNU_CFLAGS_EXTRA contains spaces and replace inner " with \"
XNU_MAKE_FLAGS += CFLAGS_EXTRA="$(subst ",\",$(XNU_CFLAGS_EXTRA))"
endif

# sources for the mocks that are linked into the same .dylib as the XNU static lib
# fake_kinit.c needs to be first because it contains the initialization entry point
# These sources are compiled against OSFMK headers with no BSD headers options (for now)
ATTACH_XNU_DYLIB_SOURCES = mocks/fake_kinit.c \
                      mocks/fake_libsptm.c \
                      mocks/mock_3rd_party.c  \
                      mocks/mock_mem.c \
                      mocks/mock_attached.c \
                      $(ATTACH_SANITIZERS_SOURCES)

# sources that are added to the compilation of every test target
TEST_SIDE_SOURCES = mocks/dt_proxy.c

# Auto-discover all other osfmk mock sources for the mocks .dylib which overrides functions from XNU
MOCK_OSFMK_SOURCES := $(shell find mocks/osfmk -name '*.c')

# Auto-discover all other bsd mock sources for the mocks .dylib which overrides functions from XNU
MOCK_BSD_SOURCES := $(shell find mocks/bsd -name '*.c')

# Auto-discover sub-folders of of tests/unit/ which contains tests
UT_NON_TEST_DIRS := mocks tools build ipc/utils/
INCLUDED_TEST_SOURCE_DIRS := $(filter-out $(UT_NON_TEST_DIRS),$(patsubst %/,%,$(wildcard */)))

# --------------------- individual tests customization ----------------------------


# ---------------------------------------------------------------------------------

# we don't want to pass our arguments to the XNU makefile since that would confuse it, but we do want to pass any
# -j argument if it existed
unexport SRCROOT
unexport SDKROOT
unexport MAKEFLAGS
MKJOBS = $(filter --jobs=%,$(MAKEFLAGS))

.FORCE:
ifeq ($(SKIP_XNU),)
# The XNU make needs to run every time since if anything changed in XNU, we want to rebuild the tests
# This extra wait time can be skipped buy adding SKIP_XNU=1 to the make command line
$(XNU_LIB_FILE): .FORCE
	SDKROOT=$(SDKROOT) $(MAKE) -C $(XNU_ROOT) RC_ProjectName=xnu_libraries XNU_LibAllFiles=1 XNU_LibFlavour=UNITTEST SDKROOT=$(SDKROOT) $(XNU_MAKE_FLAGS) $(MKJOBS)
endif # SKIP_XNU

# Structure of unit-test linking:
# Mocking of XNU functions relies on the interposable functions mechanism which requires the interposed and interposable
# functions to be in different .dylibs
# - tester (executable)
#    - compiles tester.c
#    - statically linked to libdarwintest_stripped.a  (see explanation below)
#    - statically linked to libside.a
#    - [optional] dynamically linked to tester.pmocks.dylib
#          This comes first so it overrides mocks from libmocks.dylib
#    - dynamically linked to libmocks.dylib
#    - dynamically linked to libkernel.xxx.dylib
# - libside.a
#    - compiles mocks/dt_proxy.c  ($(TEST_SIDE_SOURCES)) which bridges PT_xxx calls from libmocks and libkernel
#      to libdarwintest. This is needed since the test executable is the only mach-o which links to libdarwintest
# - libmocks_bsd.a
#    - compiles bsd mocks - $(MOCK_BSD_SOURCES) files which contain T_MOCK_X() definitions to override functions from XNU
#      via interposable functions mechanism. These mocks are compiled against bsd headers.
# - libmocks_osfmk.a
#    - compiles osfmk mocks - $(MOCK_OSFMK_SOURCES) files which contain T_MOCK_X() definitions to override functions from XNU
#      via interposable functions mechanism. These mocks are compiled against osfmk headers.
# - libmocks.dylib
#    - links libmocks_bsd.a and libmocks_osfmk.a which contain T_MOCK_X() definitions to override functions from XNU
#      via the interposable functions mechanism.
#    - dynamically linked with libkernel.xxx.dylib
# - [optional] tester.pmocks.dylib
#    - compiles code extracted from tester.c in case it has a PMOCK_START..PMOCKS_END sections
#    - dynamically linked with libkernel.xxx.dylib
# - libkernel.xxx.dylib
#    - compiles mocks/xxx.c - $(ATTACH_XNU_DYLIB_SOURCES) files which contain dependencies needed for linking the XNU static library
#    - statically linked to libkernel.xxx.prelinked.a
#    - makes all functions interposable so that internal calls are routed to mocks
# - libkernel.xxx.prelinked.a
#    - This is the same content as libkernel.xxx.a, passed through "ld -r" so that some symbols that are also
#      defined in libc can be unexported, so that anything outside XNU (i.e. mock and darwintest code) doesn't end up
#      calling them. e.g get_pid()
# - libkernel.xxx.a
#    - This is the static lib produced by XNU make which contains all of the XNU code.
# - libfuzzer.dylib
#    - The libfuzzer static lib in the SDK is linked to a dylib to avoid to statically link it to the test executable using XNU symbols

# flags that .c files under MODULE (osfmk/bsd) are built with. MODULE should be defined per target
MODULE_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)
MODULE_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)
OSFMK_CFLAGS = $(shell $(SRCROOT)/tools/quote_defines.py $(XNU_BUILD_DIR)/osfmk/$(KERNEL_CONFIG_UPPER)/.CFLAGS) -I$(XNU_BUILD_DIR)/osfmk/$(KERNEL_CONFIG_UPPER)
BSD_CFLAGS = $(shell $(SRCROOT)/tools/quote_defines.py $(XNU_BUILD_DIR)/bsd/$(KERNEL_CONFIG_UPPER)/.CFLAGS) -I$(XNU_BUILD_DIR)/bsd/$(KERNEL_CONFIG_UPPER)
MOCKS_CFLAGS = $(INTERPOSE_CFLAGS) $(COVERAGE_FLAGS) $(SANITIZERS_FLAGS)
MOCKS_OSFMK_CFLAGS = $(OSFMK_CFLAGS) $(MOCKS_CFLAGS)
MOCKS_BSD_CFLAGS = $(BSD_CFLAGS) $(MOCKS_CFLAGS)

# We need to link the darwintest headers from an empty folder and include from there since we can't add their
# original folder as -I since that would allow XNU headers to include SDK headers.
DT_SYMLINKS_DIR := $(OBJROOT)/darwintest_headers
DT_ORIG_DIR := $(SDKROOT)/usr/local/include
# Target to check if the sdk changed. content of the file is the path to the last sdk used for building
$(OBJROOT)/sdk_updated: .FORCE
	$(_v)if [ ! -f $(OBJROOT)/sdk_updated ] || [ "$$(cat $(OBJROOT)/sdk_updated)" != "$(SDKROOT)" ]; then \
	    echo $(SDKROOT) > $(OBJROOT)/sdk_updated; \
	fi
# Do we need to update the darwintest headers symlinks?
$(DT_SYMLINKS_DIR): $(DT_ORIG_DIR)/darwintest.h $(OBJROOT)/sdk_updated
	$(_v)mkdir -p $(DT_SYMLINKS_DIR)
	$(_v)for file in $(DT_ORIG_DIR)/darwintest*; do ln -sfn "$${file}" $(DT_SYMLINKS_DIR); done

# libdarwintest.a is built with normal system headers so its debug-info contains type definitions which conflict with types in XNU
# for example 'vm_map_t' is defined as `mach_port_t` (uint) instead of a struct pointer.
# This is a problem for lldb macros which get confused when trying to evaluate expressions based on these types.
# To avoid this we strip the debug info from lidarwintest and link with stripped version. There doesn't seem to be a way
# to remove just the type information or to make lldb ignore this type information.
# libdarwintest still has symbols to show in the backtrace, but no line-number information. Stepping into libdarwintest
# code is not typically needed so this doesn't affect the normal user debugging experience.
DT_ORIG_LIB := $(SDKROOT)/usr/local/lib/libdarwintest.a
DT_STRIPPED_LIB := $(OBJROOT)/libdarwintest_stripped.a
$(DT_STRIPPED_LIB): $(DT_ORIG_LIB) $(OBJROOT)/sdk_updated
	$(_v)$(STRIP) -S $(DT_ORIG_LIB) -o $(DT_STRIPPED_LIB)
# make the libdarwintest makefile not do -ldarwintest
DT_LDFLAGS :=

# The LD invocation below can't get the arch and platform from the prelinked.a file so we need this empty object
# to carry this information
ARCH_DEF_OBJ := $(OBJROOT)/arch_def.o
$(ARCH_DEF_OBJ): $(XNU_LIB_FILE)
	$(_v)$(CC) -c -x c /dev/null -o $@ $(CFLAGS)

SPTM_LIB := $(SDKROOT)/usr/local/lib/kernel/platform/libsptm_xnu.a
# this creates a dummy executable with libsptm_xnu.a so that the xbs dependency analysis know to build xnu_unittests after libsptm was installed
DUMMY_SPTM_EXE := $(OBJROOT)/ut_dummy_sptm
$(DUMMY_SPTM_EXE): $(SPTM_LIB) $(OBJROOT)/sdk_updated $(ARCH_DEF_OBJ)
	$(_v)echo "int start() { return 0; }" > $(OBJROOT)/dummy_sptm_start.c
	$(_v)$(CC) $(CFLAGS) $(OBJROOT)/dummy_sptm_start.c -Wl,-e,_start -Wl,-pie -nostdlib -Wl,-kernel -static -Wl,-force_load,$(SPTM_LIB) -o $@

# we need libfuzzer as dylib to have it using libc symbols instead of libxnu ones
LIBFUZZER_FILE := $(shell find "$(TOOLCHAIN_DIR)/usr/lib/clang" -name "libclang_rt.fuzzer_osx.a" -print -quit 2>/dev/null)
ifeq ($(LIBFUZZER_FILE),)
  $(error Could not automatically find libFuzzer (libclang_rt.fuzzer_*.a) in toolchain path: $(TOOLCHAIN_DIR))
endif
LIBFUZZER_PRELINKED := $(OBJROOT)/libfuzzer.prelinked.a
# make sure to export the sanitizercoverage symbols when prelinking
$(LIBFUZZER_PRELINKED): $(ARCH_DEF_OBJ)
	$(_v)$(LD) $(ARCH_DEF_OBJ) -r -all_load $(LIBFUZZER_FILE) -o $@ -exported_symbols_list $(SRCROOT)/tools/libfuzzer.export
LIBFUZZER_DYLIB := $(SYMROOT)/libfuzzer.dylib
$(LIBFUZZER_DYLIB): $(LIBFUZZER_PRELINKED)
	$(_v)$(CC) $(CFLAGS) -lc++ -dynamiclib $(LIBFUZZER_PRELINKED) -install_name @rpath/libfuzzer.dylib -o $@ -U _LLVMFuzzerTestOneInput
MAY_LINK_LIBFUZZER =
# we need to link libxnu and libmocks to libfuzzer.dylib to avoid symbol binding to the ubsan dylib
ifeq ($(BUILD_LIBFUZZER),1)
	MAY_LINK_LIBFUZZER = $(LIBFUZZER_DYLIB)
endif

XNU_LIB_PRELINKED := $(OBJROOT)/$(XNU_LIB_FILE_BASE).prelinked.a
$(XNU_LIB_PRELINKED): $(XNU_LIB_FILE) $(SRCROOT)/tools/xnu_lib.unexport $(ARCH_DEF_OBJ)
	$(_v)$(LD) -r $(ARCH_DEF_OBJ) -all_load $(XNU_LIB_FILE) -o $@ -unexported_symbols_list $(SRCROOT)/tools/xnu_lib.unexport -alias_list $(XNU_ALIAS_LIST)

XNU_LIB_DYLIB := $(SYMROOT)/$(XNU_LIB_FILE_BASE).dylib

$(XNU_LIB_DYLIB): $(XNU_LIB_PRELINKED) $(ATTACH_XNU_DYLIB_SOURCES) $(DT_SYMLINKS_DIR) $(MAY_LINK_LIBFUZZER)
	$(_v)$(CC) $(MOCKS_OSFMK_CFLAGS) $(CFLAGS) -dynamiclib $(ATTACH_XNU_DYLIB_SOURCES) -Wl,-all_load,$(XNU_LIB_PRELINKED) -lc++ -Wl,-undefined,dynamic_lookup -Wl,-interposable -install_name @rpath/$(XNU_LIB_FILE_BASE).dylib $(MAY_LINK_LIBFUZZER) -o $@
ifneq ($(NO_LLDBMACROS),1)
	$(_v)cp -R $(XNU_LIB_DSYM)/Contents/Resources/Python $@.dSYM/Contents/Resources/
	$(_v)mv $@.dSYM/Contents/Resources/Python/{,lib}$(XNU_FILE_NAME_PREFIX)_$(KERNEL_CONFIG).py
endif

# Do we need to update the unimplemented functions mock?
$(OBJROOT)/func_unimpl.inc: $(XNU_LIB_DYLIB)
	$(_v)echo "// Generated from undefined imports from: $(XNU_LIB_DYLIB)" > $@
	$(_v)$(DYLD_INFO) -imports $(XNU_LIB_DYLIB) | grep "flat-namespace" | awk '{print "UNIMPLEMENTED(" substr($$2, 2) ")"}' >> $@
# The xnu dylib is linked with -undefined dynamic_lookup so that it's able to find undefined symbols at load time
# These are symbols that come from libsptm_xnu.a and libTightbeam.a. They still need to have an implementation for load
# to succeeds so this gets the list of undefined symbols and defines them in the mocks dylib
# use awk to discard the first underscore prefix of each symbol and wrap with UNIMPLEMENTED() macro expected in mock_unimpl.c

MOCKS_OSFMK_LIB := $(SYMROOT)/libmocks_osfmk.a
MOCKS_OSFMK_OBJECTS := $(patsubst $(OBJROOT)/mocks/%,$(OBJROOT)/%,$(patsubst %.c,$(OBJROOT)/%.o,$(MOCK_OSFMK_SOURCES)))
$(OBJROOT)/osfmk/%.o: mocks/osfmk/%.c $(XNU_LIB_DYLIB) $(OBJROOT)/func_unimpl.inc
	$(_v)mkdir -p $(OBJROOT)/osfmk/fibers
	$(_v)$(CC) $(MOCKS_OSFMK_CFLAGS) -I$(OBJROOT) -DUT_BUILDING_LIBMOCKS $(CFLAGS) -c $< -o $@

$(MOCKS_OSFMK_LIB): $(MOCKS_OSFMK_OBJECTS)
	$(_v)$(LIBTOOL) -static $^ -o $@

MOCKS_BSD_LIB := $(SYMROOT)/libmocks_bsd.a
MOCKS_BSD_OBJECTS := $(patsubst mocks/bsd/%.c,$(OBJROOT)/bsd/%.o,$(MOCK_BSD_SOURCES))
$(OBJROOT)/bsd/%.o:  mocks/bsd/%.c $(XNU_LIB_DYLIB) $(OBJROOT)/func_unimpl.inc
	$(_v)mkdir -p $(OBJROOT)/bsd
	$(_v)$(CC) $(MOCKS_BSD_CFLAGS) -I$(OBJROOT) -DUT_BUILDING_LIBMOCKS $(CFLAGS) -c $< -o $@

$(MOCKS_BSD_LIB): $(MOCKS_BSD_OBJECTS)
	$(_v)$(LIBTOOL) -static $^ -o $@

MOCKS_DYLIB := $(SYMROOT)/libmocks.dylib
LIBMOCKS_MOCKSYMS := $(OBJROOT)/libmocks.mocksyms
$(MOCKS_DYLIB): $(XNU_LIB_DYLIB) $(MOCKS_BSD_LIB) $(MOCKS_OSFMK_LIB) $(OBJROOT)/func_unimpl.inc $(MAY_LINK_LIBFUZZER)
	$(_v)$(CC) $(MOCKS_OSFMK_CFLAGS) $(CFLAGS) -I$(OBJROOT) $(XNU_LIB_DYLIB) -Wl,-all_load,$(MOCKS_BSD_LIB) -Wl,-all_load,$(MOCKS_OSFMK_LIB) -dynamiclib -MJ $@.json -install_name @rpath/libmocks.dylib $(MAY_LINK_LIBFUZZER) -o $@
	$(_v)$(DSYMUTIL) $@ -o $@.dSYM
	$(_v)$(NM) -gjU $@ | grep "_MOCK_" > $(LIBMOCKS_MOCKSYMS)
# $(CC) ... -install_name makes the test executables which link to these .dylibs find them in the same folder rather than the
# folder the .dylibs happen to be built at. The path is relative to rpath and rpath is defined by the executable
# itself to be to root of test executables, see below.
# $(DSYMUNIT) - The clang driver only invokes ld and doesn't generate the dsym on it's own
# $(NM) ... > .mocksyms lists all the mock functions created in libmocks.dylib this will be used when creating the .pmocks.dylib
# to check for double-defined mocks.

SIDE_LIB := $(OBJROOT)/libside.a
TEST_SIDE_OBJ := $(foreach source,$(TEST_SIDE_SOURCES),$(OBJROOT)/$(notdir $(basename $(source))).o)
$(TEST_SIDE_OBJ): $(TEST_SIDE_SOURCES) $(DT_SYMLINKS_DIR) $(XNU_LIB_DYLIB)
	$(_v)$(CC) $(MOCKS_OSFMK_CFLAGS) $(CFLAGS) $(DT_CFLAGS) -c $< -o $@
$(SIDE_LIB): $(TEST_SIDE_OBJ)
	$(_v)$(LIBTOOL) -static $< -o $@

# this creates a shell script for running all the unit-tests on-desk (build all using target 'install'
.PHONY: run_unittests
install: run_unittests
RUN_UT_SCRIPT := $(SYMROOT)/run_unittests.sh
RUN_UT_LIST := $(SYMROOT)/run_unittests.targets
run_unittests: $(RUN_UT_SCRIPT)
$(RUN_UT_SCRIPT): $(SRCROOT)/tools/make_run_unittests.py
	$(SRCROOT)/tools/make_run_unittests.py "$(TEST_TARGETS)" $@ $(RUN_UT_LIST)
	chmod a+x $@

# inform the dt makefile that these need to be installed along with the test executables
CUSTOM_TARGETS += $(XNU_LIB_DYLIB) $(MOCKS_DYLIB) $(DUMMY_SPTM_EXE) $(LIBFUZZER_DYLIB) $(RUN_UT_SCRIPT)
install-$(XNU_LIB_DYLIB): $(XNU_LIB_DYLIB)
	mkdir -p $(INSTALLDIR)
	cp $< $(INSTALLDIR)/
install-$(MOCKS_DYLIB): $(MOCKS_DYLIB)
	mkdir -p $(INSTALLDIR)
	cp $< $(INSTALLDIR)/
install-$(DUMMY_SPTM_EXE): $(DUMMY_SPTM_EXE)
	echo "not copying $(DUMMY_SPTM_EXE)"
install-$(LIBFUZZER_DYLIB): $(LIBFUZZER_DYLIB)
	mkdir -p $(INSTALLDIR)
	cp $< $(INSTALLDIR)/
install-$(RUN_UT_SCRIPT): $(RUN_UT_SCRIPT)
	mkdir -p $(INSTALLDIR)
	cp $< $(INSTALLDIR)/
	cp $(RUN_UT_LIST) $(INSTALLDIR)/

OTHER_LDFLAGS += $(XNU_LIB_DYLIB) $(MOCKS_DYLIB) $(PMOCKS_DYLIB_IF_EXIST) -Wl,-force_load,$(SIDE_LIB)

# add libfuzzer flags and rpath as the xnu and mocks lib will be not located in the same directory
fuzzing/%: OTHER_CFLAGS += -fsanitize=fuzzer-no-link
# linking libfuzzer.dylib for fuzzing harnesses first as it is important to prioritize its symbols
fuzzing/%: OTHER_LDFLAGS := $(LIBFUZZER_DYLIB) $(OTHER_LDFLAGS)

include $(DEVELOPER_DIR)/AppleInternal/Makefiles/darwintest/Makefile.targets

# ---- pmocks ----
# A test can define private mocks (pmocks), i.e. mocks that are going to be defined only when it runs.
# This is done by adding a section sorrounded by PMOCKS_START..PMOCKS_END and adding T_MOCK_PRIVATE()
# definitions in it.
# This is different from using T_MOCK_SET_CALLBACK() where the mock is defined in the global libmocks
# and the test supplies a function that will be called when the mock is called.
# These are two mechanisms to do the same thing - allow the test to provide its own implementation for a mock
# pmocks have the added benefit of avoiding possible code conflicts when adding to the global mocks library
# in tests/unit/mocks/
#
# dyld-interposing works only between two .dylibs
# We extract the code between PMOCKS_START..PMOCKS_END from test.c into a separate test.pmocks.c file
# and build it into test.pmocks.dylib which the test executable depends on

.SECONDEXPANSION:
# This expands to the name of the pmocks.dylib if the test.c includes PMOCKS_START or to nothing if not
# This is added to the test executable dependencies
# The above .SECONDEXPANSION: is needed for this to expand correctly
CHECK_PMOCKS = $(shell \
  for f in $*.c $*.cpp; do \
    [ -f "$$f" ] && grep -q 'PMOCKS_START' "$$f" && { echo $(SYMROOT)/$*.pmocks.dylib; break; }; \
  done)

PMOCKS_DYLIBS_c := $(patsubst %,$(SYMROOT)/%.pmocks.dylib,$(TEST_TARGETS_c))
PMOCKS_DYLIBS_cxx := $(patsubst %,$(SYMROOT)/%.pmocks.dylib,$(TEST_TARGETS_cxx))

# recipe for generating test.pmocks.dylib
# - verify there is a PMOCKS_END (after the above check found we have a PMOCKS_START
# - run test.c through the preprocessor so that conditionally compiled code can be removed to produce test.pmocks.i
#    - MODULE_CFLAGS is available here due to the dependency from the target
# - extract only the sections inside PMOCKS_START..PMOCKS_END. There can be multiple such sections to produce test.pmocks.c
# - build test.pmocks.dylib from the extracted code
# - verify the mocks produced in the built dylib do not intersect with the mocks in libmocks.dylib
#   - Having the same mock in two dylibs causes hard to predict bugs
define PMOCKS_RECIPE
	$(_v)grep -q 'PMOCKS_END' $< || (echo "Error: Missing PMOCKS_END in $<" >&2; exit 1)
	$(_v)$(CC) -E -P $(MODULE_CFLAGS) $(CFLAGS) $(DT_CFLAGS) $(INTERPOSE_CFLAGS) -I$(OBJROOT) -DUT_EXTRACTING_PMOCKS $< -o $(OBJROOT)/$(subst /,__,$*).pmocks.i
	$(_v)awk '/^PMOCKS_START/{f=1;next}/^PMOCKS_END/{f=0}f' < $(OBJROOT)/$(subst /,__,$*).pmocks.i > $(OBJROOT)/$(subst /,__,$*).pmocks.c
	$(_v)rm -f $(OBJROOT)/$(subst /,__,$*).pmocks.i
	$(_v)mkdir -p $(@D)
	$(_v)$(CC) $(MODULE_CFLAGS) $(MOCKS_CFLAGS) -DUT_BUILDING_PMOCKS $(CFLAGS) -I$(OBJROOT) $(XNU_LIB_DYLIB) -include mocks/mock_dynamic.h -dynamiclib -install_name @rpath/$*.pmocks.dylib $(OBJROOT)/$(subst /,__,$*).pmocks.c -o $@
	$(_v)$(NM) -gjU $@ | grep "_MOCK_" > $(OBJROOT)/$(subst /,__,$*).mocksyms
	$(_v)SYM_INTERSECT=$$(comm -12 $(LIBMOCKS_MOCKSYMS) $(OBJROOT)/$(subst /,__,$*).mocksyms) || exit 1; \
    	if [ -n "$$SYM_INTERSECT" ]; then \
    		echo "Error: pmocks section in '$*' defines a mock that's already defined in libmocks.dylib. consider using T_MOCK_SET_PERM_FUNC() instead."; \
    		echo "$$SYM_INTERSECT"; \
    		rm -f $@; \
    		exit 1; \
    	fi
endef

# Separate rules for .c and .cpp since they depend on a different pattern
$(PMOCKS_DYLIBS_c): $(SYMROOT)/%.pmocks.dylib: %.c $(XNU_LIB_DYLIB) $(MOCKS_DYLIB) $(DT_SYMLINKS_DIR)
	$(PMOCKS_RECIPE)
$(PMOCKS_DYLIBS_cxx): $(SYMROOT)/%.pmocks.dylib: %.cpp $(XNU_LIB_DYLIB) $(MOCKS_DYLIB) $(DT_SYMLINKS_DIR)
	$(PMOCKS_RECIPE)

# This is added to OTHER_LDFLAGS so that the test-executable links to the pmocks.dylib if it exists
PMOCKS_DYLIB_IF_EXIST = $(shell [ -f $(SYMROOT)/$*.pmocks.dylib ] && echo $(SYMROOT)/$*.pmocks.dylib)

.PHONY: install_pmocks
install: install_pmocks
install_pmocks: $(TEST_TARGETS)
	@mkdir -p $(INSTALLDIR)
	@for t in $(TEST_TARGETS); do \
		pmock=$$t.pmocks.dylib; \
		if [ -f "$(SYMROOT)/$$pmock" ]; then \
		    echo "installing pmock $(SYMROOT)/$$pmock to dir $(INSTALLDIR)/$$(dirname $$pmock)"; \
		    mkdir -p $(INSTALLDIR)/$$(dirname $$pmock); \
			cp $(SYMROOT)/$$pmock $(INSTALLDIR)/$$(dirname $$pmock); \
		fi; \
	done


# ---- determining the module for the test ----
# Every unit-test target needs to define a target-specific variable named UT_MODULE so that MODULE_CFLAGS is defined
# This is parsed from the .c file that needs to have a line like: #define UT_MODULE osfmk
# The clang invocation may produce errors due to missing include folders but it still generates the #defines list.
define assign_module
$(1): MODULE ?= $(shell for f in $(1).c $(1).cpp; do \
    [ -f $$f ] && $(CC) -E -dM $$f 2>/dev/null | grep -m1 '^#define UT_MODULE ' | awk '{print $$3}' && break; \
done)
endef
$(foreach target, $(TEST_TARGETS), $(eval $(call assign_module, $(target), $(target))))
# In the case no UT_MODULE was found, this will trigger an error in std_safe.h
MODULE_FLAG = -DUT_MODULE=$(if $(strip $(MODULE)),$(MODULE),-1)

# All test targets depend on the XNU lib
$(TEST_TARGETS): $(XNU_LIB_DYLIB) $(MOCKS_DYLIB) $(SIDE_LIB) $(LIBFUZZER_DYLIB) $(DT_STRIPPED_LIB) $$(CHECK_PMOCKS)

# When the test executalbe is in a sub-folder, it's rpath needs to point back to the root of all
# test executables. This is done by appending as many ".." to @executable_path as there are levels of sub-directories
define make_rpath
@executable_path$(shell \
  dir=$$(dirname "$1"); \
  if [ "$$dir" != "." ]; then \
    echo "$$dir" | sed 's|[^/][^/]*|..|g' | sed 's|^|/|'; \
  fi)
endef
# this sets the CFLAGS for building the test to be the same as files in it's UT_MODULE
$(TEST_TARGETS): OTHER_CFLAGS += $(MODULE_CFLAGS) -MJ $(OBJROOT)/$(subst /,__,$@).json -rpath $(call make_rpath,$@)
# C++ files get both CFLAGS and CXXFLAGS
$(TEST_TARGETS): OTHER_CXXFLAGS += $(MODULE_CXXFLAGS) $(MODULE_CFLAGS) -MJ $(OBJROOT)/$(subst /,__,$@).json -rpath $(call make_rpath,$@)

# This is for creating a new version of $(XNU_ROOT)/compile_commands.json that includes
# the test and mock files. It's used by IDEs for understanding the code
.PHONY: cmds_json proj_xcode proj_vscode proj_clion
cmds_json:
	$(SRCROOT)/tools/merge_cmds_json.py $(XNU_ROOT) $(XNU_BUILD_DIR) $(OBJROOT)

proj_xcode: cmds_json
	$(SRCROOT)/tools/generate_ut_proj.py xcode
proj_vscode: cmds_json
	$(SRCROOT)/tools/generate_ut_proj.py vscode
proj_clion: cmds_json
	$(SRCROOT)/tools/generate_ut_proj.py clion