├── .clang-format ├── .gitattributes ├── .gitconfig ├── .gitignore ├── .gitmodules ├── .pre-commit-config.yaml ├── 3rdparty ├── CMakeLists.txt ├── icecream-scoped-include │ └── icecream.hpp ├── simde-scoped-include │ └── simde └── xxhash-scoped-include │ └── xxhash-xnu-trace │ └── xxhash.h ├── CMakeLists.txt ├── LICENSE ├── README.md ├── docs ├── TODO.md ├── arm64-mem-opc.txt ├── atomic-bits-alt.png ├── atomic-bits-alt.svg ├── atomic-bits.png ├── atomic-bits.svg ├── dyld-ventura-vmmap-after.txt ├── dyld-ventura-vmmap-before.txt ├── getbits.md ├── log_msg_hdr_gpr_bitfield.json ├── log_msg_hdr_mem_bitfield.json ├── notes.txt ├── perf.txt ├── render-data-structures.json ├── render-data-structures.svg ├── sleep-loop.txt └── whitebox.txt ├── include └── xnu-trace │ ├── ARM64Disassembler.h │ ├── ARM64InstrHistogram.h │ ├── Atomic.h │ ├── BitVector.h │ ├── CompressedFile.h │ ├── EliasFano.h │ ├── FridaStalker.h │ ├── MachORegions.h │ ├── MinimalPerfectHash.h │ ├── RankSelect.h │ ├── Signpost.h │ ├── Symbols.h │ ├── ThreadPool.h │ ├── TraceLog.h │ ├── VMRegions.h │ ├── XNUCommpageTime.h │ ├── XNUTracer.h │ ├── common-c.h │ ├── common.h │ ├── drcov.h │ ├── dyld.h │ ├── log_structs.h │ ├── mach.h │ ├── macho.h │ ├── proc.h │ ├── utils.h │ ├── xnu-trace-c.h │ └── xnu-trace.h ├── jupyter-notebooks ├── atomic-bitvector-cairo.ipynb ├── atomic-bitvector-matplotlib.ipynb ├── bitidx.ipynb ├── drawing.py ├── numba-tests.ipynb ├── render.ipynb └── transmorgifier.ipynb ├── lib ├── CMakeLists.txt └── xnu-trace │ ├── ARM64Disassembler.cpp │ ├── ARM64InstrHistogram.cpp │ ├── CMakeLists.txt │ ├── CompressedFile.cpp │ ├── FridaStalker.cpp │ ├── MachORegions.cpp │ ├── MinimalPerfectHash.cpp │ ├── Signpost.cpp │ ├── Symbols.cpp │ ├── ThreadPool.cpp │ ├── TraceLog.cpp │ ├── VMRegions.cpp │ ├── XNUCommpageTime.cpp │ ├── XNUTracer.cpp │ ├── common-internal.h │ ├── dyld.cpp │ ├── exception_handlers.cpp │ ├── log_structs.cpp │ ├── mach.cpp │ ├── mach_exc.defs │ ├── macho.cpp │ ├── proc.cpp │ └── utils.cpp ├── python ├── setup.cfg ├── setup.py └── xnutrace │ ├── __init__.py │ ├── compressedfile.py │ ├── tools │ ├── __init__.py │ ├── find_loops.py │ ├── render_trace.py │ └── transmorgifier.py │ └── tracelog.py ├── scripts ├── atomic_bitvector.py ├── atomic_bitvector_diagram.py ├── atomic_bitvector_simplify.py ├── instr_bitmask_decode_z3.py ├── instr_intersect.py ├── perfect_hashing.py └── swar-z3.py ├── test ├── CMakeLists.txt ├── hana-101.cpp ├── iterator-proxy.cpp ├── page_addrs.bin ├── page_addrs.txt.zst ├── rand_u64_dup_idx_29751.bin ├── vtable-opt.cpp ├── xnu-fast-timeout.cpp ├── xnu-trace-bench.cpp ├── xnu-trace-mph.cpp ├── xnu-trace-nanobench.cpp └── yield-loop-step-test.cpp ├── tools ├── CMakeLists.txt ├── XNUTracer │ ├── XNUTracer.xcodeproj │ │ ├── project.pbxproj │ │ └── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── XNUTracer │ │ ├── AppDelegate.h │ │ ├── AppDelegate.m │ │ ├── Assets.xcassets │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── Contents.json │ │ ├── Base.lproj │ │ └── Main.storyboard │ │ ├── ViewController.h │ │ ├── ViewController.m │ │ ├── XNUTracer.entitlements │ │ └── main.m ├── capstone-arm64-enumerate-mem-insn │ ├── CMakeLists.txt │ └── capstone-arm64-enumerate-mem-insn.cpp ├── spawn-no-aslr │ ├── CMakeLists.txt │ └── spawn-no-aslr.cpp ├── xnu-trace-compressed-file-util │ ├── CMakeLists.txt │ └── xnu-trace-compressed-file-util.cpp ├── xnu-trace-log-util │ ├── CMakeLists.txt │ └── xnu-trace-log-util.cpp ├── xnu-trace-render │ ├── CMakeLists.txt │ └── xnu-trace-render.cpp └── xnu-trace-single-step-runner │ ├── CMakeLists.txt │ ├── ent.xml │ └── xnu-trace-single-step-runner.cpp └── unit-tests ├── ARM64Disassembler.cpp ├── BitVector.cpp ├── CMakeLists.txt ├── EliasFano.cpp ├── MinimalPerfectHash.cpp ├── RankSelect.cpp ├── memmem-chunking.cpp └── xnu-trace-unit-tests-catch2.cpp /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: LLVM 2 | IndentWidth: 4 3 | ObjCBlockIndentWidth: 4 4 | UseTab: Never 5 | ColumnLimit: 100 6 | AccessModifierOffset: -4 7 | AllowShortBlocksOnASingleLine: Empty 8 | AllowShortFunctionsOnASingleLine: Empty 9 | AllowShortCaseLabelsOnASingleLine: false 10 | AllowShortEnumsOnASingleLine: false 11 | AllowShortIfStatementsOnASingleLine: Never 12 | AllowShortLambdasOnASingleLine: Empty 13 | AllowShortLoopsOnASingleLine: false 14 | AlignConsecutiveAssignments: Consecutive 15 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.ipynb merge=nbdev-merge 2 | -------------------------------------------------------------------------------- /.gitconfig: -------------------------------------------------------------------------------- 1 | # Generated by nbdev_install_hooks 2 | # 3 | # If you need to disable this instrumentation do: 4 | # git config --local --unset include.path 5 | # 6 | # To restore: 7 | # git config --local include.path ../.gitconfig 8 | # 9 | [merge "nbdev-merge"] 10 | name = resolve conflicts with nbdev_fix 11 | driver = nbdev_merge %O %A %B %P 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # vscode 2 | **/.vscode/ 3 | 4 | # build folder 5 | /build/ 6 | /build-opt/ 7 | /build-dbg/ 8 | 9 | # mac 10 | **/.DS_Store 11 | 12 | # xcode 13 | **/DerivedData 14 | **/xcuserdata 15 | 16 | # python 17 | **/__pycache__ 18 | **/*.egg-info 19 | 20 | # jupyter-notebook 21 | **/*.ipynb_checkpoints 22 | 23 | # nbdev 24 | .last_checked 25 | .irecovery 26 | 27 | /.cache/ 28 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "3rdparty/fmt"] 2 | path = 3rdparty/fmt 3 | url = https://github.com/fmtlib/fmt 4 | [submodule "3rdparty/argparse"] 5 | path = 3rdparty/argparse 6 | url = https://github.com/p-ranav/argparse 7 | [submodule "3rdparty/CoreSymbolication"] 8 | path = 3rdparty/CoreSymbolication 9 | url = https://github.com/jevinskie/CoreSymbolication-cmake 10 | [submodule "CoreSymbolication"] 11 | branch = jev/main 12 | [submodule "3rdparty/interval-tree"] 13 | path = 3rdparty/interval-tree 14 | url = https://github.com/5cript/interval-tree 15 | [submodule "3rdparty/zstd"] 16 | path = 3rdparty/zstd 17 | url = https://github.com/facebook/zstd 18 | [submodule "3rdparty/metal-cpp"] 19 | path = 3rdparty/metal-cpp 20 | url = https://github.com/jevinskie/metal-cpp-mirror 21 | branch = cmake 22 | [submodule "3rdparty/mbedtls"] 23 | path = 3rdparty/mbedtls 24 | url = https://github.com/Mbed-TLS/mbedtls 25 | [submodule "3rdparty/arch-arm64"] 26 | path = 3rdparty/arch-arm64 27 | url = https://github.com/jevinskie/arch-arm64 28 | [submodule "3rdparty/perf-macos"] 29 | path = 3rdparty/perf-macos 30 | url = https://github.com/jevinskie/perf-macos 31 | [submodule "3rdparty/benchmark"] 32 | path = 3rdparty/benchmark 33 | url = https://github.com/jevinskie/benchmark 34 | [submodule "3rdparty/abseil-cpp"] 35 | path = 3rdparty/abseil 36 | url = https://github.com/abseil/abseil-cpp 37 | [submodule "3rdparty/thread-pool"] 38 | path = 3rdparty/thread-pool 39 | url = https://github.com/bshoshany/thread-pool 40 | [submodule "3rdparty/xxHash"] 41 | path = 3rdparty/xxHash 42 | url = https://github.com/Cyan4973/xxHash 43 | [submodule "3rdparty/libdivide"] 44 | path = 3rdparty/libdivide 45 | url = https://github.com/jevinskie/libdivide 46 | branch = jev/main 47 | [submodule "3rdparty/compact_vector"] 48 | path = 3rdparty/compact_vector 49 | url = https://github.com/jevinskie/compact_vector 50 | branch = jev/main 51 | [submodule "3rdparty/fastmod"] 52 | path = 3rdparty/fastmod 53 | url = https://github.com/jevinskie/fastmod 54 | branch = jev/main 55 | [submodule "3rdparty/nanobench"] 56 | path = 3rdparty/nanobench 57 | url = https://github.com/martinus/nanobench 58 | [submodule "3rdparty/range-v3"] 59 | path = 3rdparty/range-v3 60 | url = https://github.com/ericniebler/range-v3 61 | [submodule "3rdparty/simde"] 62 | path = 3rdparty/simde 63 | url = https://github.com/simd-everywhere/simde 64 | [submodule "3rdparty/static_vector"] 65 | path = 3rdparty/static_vector 66 | url = https://github.com/jevinskie/static_vector 67 | [submodule "static_vector"] 68 | branch = jev/main 69 | [submodule "3rdparty/Catch2"] 70 | path = 3rdparty/Catch2 71 | url = https://github.com/catchorg/Catch2 72 | [submodule "3rdparty/icecream-cpp"] 73 | path = 3rdparty/icecream-cpp 74 | url = https://github.com/renatoGarcia/icecream-cpp 75 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # Note the order is intentional to avoid multiple passes of the hooks 2 | minimum_pre_commit_version: 2.9.0 3 | repos: 4 | - repo: meta 5 | hooks: 6 | - id: check-hooks-apply 7 | - id: check-useless-excludes 8 | - repo: https://github.com/pre-commit/pre-commit-hooks 9 | rev: v5.0.0 10 | hooks: 11 | - id: check-case-conflict 12 | - id: check-shebang-scripts-are-executable 13 | - id: check-yaml 14 | - id: check-vcs-permalinks 15 | - id: detect-private-key 16 | - id: end-of-file-fixer 17 | - id: mixed-line-ending 18 | - id: fix-byte-order-marker 19 | - id: check-merge-conflict 20 | - id: trailing-whitespace 21 | args: [--markdown-linebreak-ext=md] 22 | - repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks 23 | rev: v2.14.0 24 | hooks: 25 | - id: pretty-format-yaml 26 | args: [--autofix, --indent, '2'] 27 | - repo: https://github.com/pre-commit/mirrors-clang-format 28 | rev: v19.1.4 29 | hooks: 30 | - id: clang-format 31 | types_or: [c++, c, objective-c, objective-c++] 32 | - repo: https://github.com/pre-commit/pre-commit-hooks 33 | rev: v5.0.0 34 | hooks: 35 | - id: trailing-whitespace 36 | - id: end-of-file-fixer 37 | -------------------------------------------------------------------------------- /3rdparty/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(arch-arm64/disassembler) 2 | 3 | add_subdirectory(argparse) 4 | 5 | option(ZSTD_LEGACY_SUPPORT "" OFF) 6 | option(ZSTD_BUILD_PROGRAMS "" ON) 7 | option(ZSTD_BUILD_STATIC "" ON) 8 | option(ZSTD_BUILD_SHARED "" OFF) 9 | add_subdirectory(zstd/build/cmake) 10 | target_include_directories(libzstd_static 11 | PUBLIC 12 | $ 13 | $ 14 | ) 15 | 16 | add_subdirectory(CoreSymbolication) 17 | 18 | add_subdirectory(fmt) 19 | 20 | option(INT_TREE_ENABLE_TESTS "" OFF) 21 | add_subdirectory(interval-tree) 22 | 23 | option(ENABLE_PROGRAMS "" OFF) 24 | option(ENABLE_TESTING "" OFF) 25 | add_subdirectory(mbedtls) 26 | 27 | # add_subdirectory(perf-macos) 28 | 29 | add_subdirectory(abseil) 30 | # add_subdirectory(compact_vector) 31 | add_subdirectory(fastmod) 32 | add_subdirectory(range-v3) 33 | 34 | add_library(static_vector INTERFACE static_vector/include/experimental/fixed_capacity_vector) 35 | target_include_directories(static_vector INTERFACE static_vector/include) 36 | target_compile_definitions(static_vector INTERFACE FCV_NDEBUG) 37 | target_compile_options(static_vector INTERFACE -include cassert) 38 | 39 | add_library(thread-pool INTERFACE) 40 | target_include_directories(thread-pool INTERFACE thread-pool) 41 | 42 | add_library(xxhash-xnu-trace INTERFACE) 43 | target_include_directories(xxhash-xnu-trace INTERFACE xxhash-scoped-include) 44 | 45 | add_library(simde INTERFACE) 46 | target_include_directories(simde INTERFACE simde-scoped-include) 47 | 48 | find_package(Boost CONFIG REQUIRED headers) 49 | set_target_properties(Boost::headers PROPERTIES IMPORTED_GLOBAL ON) 50 | 51 | add_library(icecream INTERFACE) 52 | target_include_directories(icecream INTERFACE icecream-scoped-include) 53 | 54 | add_subdirectory(Catch2) 55 | 56 | set(BENCHMARK_ENABLE_TESTING OFF) 57 | add_subdirectory(benchmark) 58 | add_subdirectory(nanobench) 59 | 60 | cmake_policy(SET CMP0135 NEW) 61 | include(FetchContent) 62 | FetchContent_Declare( 63 | frida-gum-prebuilt 64 | FETCHCONTENT_TRY_FIND_PACKAGE_MODE NEVER 65 | URL https://github.com/frida/frida/releases/download/16.5.7/frida-gum-devkit-16.5.7-macos-arm64.tar.xz 66 | ) 67 | FetchContent_MakeAvailable(frida-gum-prebuilt) 68 | FetchContent_GetProperties(frida-gum-prebuilt SOURCE_DIR FRIDA_GUM_SRC_DIR) 69 | 70 | add_library(frida-gum STATIC IMPORTED GLOBAL) 71 | set_target_properties(frida-gum PROPERTIES IMPORTED_LOCATION ${FRIDA_GUM_SRC_DIR}/libfrida-gum.a) 72 | target_include_directories(frida-gum INTERFACE ${FRIDA_GUM_SRC_DIR}) 73 | -------------------------------------------------------------------------------- /3rdparty/icecream-scoped-include/icecream.hpp: -------------------------------------------------------------------------------- 1 | #pragma GCC diagnostic push 2 | #pragma GCC diagnostic ignored "-Wdeprecated-declarations" 3 | #include "../icecream-cpp/icecream.hpp" 4 | #pragma GCC diagnostic pop 5 | -------------------------------------------------------------------------------- /3rdparty/simde-scoped-include/simde: -------------------------------------------------------------------------------- 1 | ../simde/simde -------------------------------------------------------------------------------- /3rdparty/xxhash-scoped-include/xxhash-xnu-trace/xxhash.h: -------------------------------------------------------------------------------- 1 | ../../xxHash/xxhash.h -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.31) 2 | 3 | project(xnu-trace CXX C) 4 | 5 | set(BUILD_SHARED_LIBS OFF) 6 | 7 | # set(CMAKE_OSX_ARCHITECTURES arm64e) 8 | set(CMAKE_XCODE_ATTRIBUTE_COMPILER_INDEX_STORE_ENABLE NO) 9 | 10 | option(FORCE_COLORED_OUTPUT "Always produce ANSI-colored output (GNU/Clang only)." ON) 11 | 12 | if (${FORCE_COLORED_OUTPUT}) 13 | if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") 14 | add_compile_options(-fdiagnostics-color=always) 15 | elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang") 16 | add_compile_options(-fcolor-diagnostics) 17 | endif () 18 | endif () 19 | 20 | add_compile_options(-fstrict-vtable-pointers) 21 | add_link_options(-fstrict-vtable-pointers) 22 | 23 | add_compile_options(-ggdb3) 24 | # add_compile_options(-g0) 25 | 26 | # add_compile_options(-O0 -fno-omit-frame-pointer) 27 | 28 | # add_compile_options(-Os -fvisibility=hidden -fvisibility-inlines-hidden -ffunction-sections -fdata-sections) 29 | # add_link_options(-Os -Wl,-dead_strip) 30 | 31 | add_compile_options(-Og) 32 | add_link_options(-Og) 33 | 34 | add_compile_options(-fsanitize=address) 35 | add_link_options(-fsanitize=address) 36 | 37 | # add_compile_options(-fsanitize=memory) 38 | # add_link_options(-fsanitize=memory) 39 | 40 | # add_compile_options(-O3) 41 | # add_link_options(-O3) 42 | 43 | # add_compile_options(-Ofast) 44 | # add_link_options(-Ofast) 45 | 46 | # add_compile_options(-flto=full -fwhole-program-vtables -fvirtual-function-elimination) 47 | # add_link_options(-flto=full) 48 | 49 | # lol just don't its so bad 50 | # add_compile_options(-fno-inline) 51 | # add_link_options(-fno-inline) 52 | 53 | if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang") 54 | add_compile_options(-ferror-limit=100) 55 | add_compile_options(-Wno-documentation) 56 | endif() 57 | 58 | set(CMAKE_CXX_STANDARD 23) 59 | set(CMAKE_CXX_EXTENSIONS YES) 60 | 61 | option(FMT_DOC OFF) 62 | option(FMT_TEST OFF) 63 | add_subdirectory(3rdparty) 64 | 65 | add_subdirectory(lib) 66 | add_subdirectory(test) 67 | add_subdirectory(tools) 68 | add_subdirectory(unit-tests) 69 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2022, Jevin Sweval 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # xnu-trace 2 | Tracing of iOS/macOS binaries using HW single step and Frida DBI. 3 | 4 | ## SAST Tools 5 | [PVS-Studio](https://pvs-studio.com/en/pvs-studio/?utm_source=website&utm_medium=github&utm_campaign=open_source) - static analyzer for C, C++, C#, and Java code. 6 | -------------------------------------------------------------------------------- /docs/TODO.md: -------------------------------------------------------------------------------- 1 | - [ ] xnu-tracer-instruction-stats 2 | 3 | - [ ] trace.bundle/macho-regions.bin to store instructions 4 | 5 | - [ ] Fast branch detector 6 | -------------------------------------------------------------------------------- /docs/arm64-mem-opc.txt: -------------------------------------------------------------------------------- 1 | opc: cas 2 | opc: casa 3 | opc: casab 4 | opc: casah 5 | opc: casal 6 | opc: casalb 7 | opc: casalh 8 | opc: casb 9 | opc: cash 10 | opc: casl 11 | opc: caslb 12 | opc: caslh 13 | opc: casp 14 | opc: caspa 15 | opc: caspal 16 | opc: caspl 17 | opc: ld1 18 | opc: ld1b 19 | opc: ld1d 20 | opc: ld1h 21 | opc: ld1r 22 | opc: ld1rb 23 | opc: ld1rd 24 | opc: ld1rh 25 | opc: ld1rqb 26 | opc: ld1rqd 27 | opc: ld1rqh 28 | opc: ld1rqw 29 | opc: ld1rsb 30 | opc: ld1rsh 31 | opc: ld1rsw 32 | opc: ld1rw 33 | opc: ld1sb 34 | opc: ld1sh 35 | opc: ld1sw 36 | opc: ld1w 37 | opc: ld2 38 | opc: ld2b 39 | opc: ld2d 40 | opc: ld2h 41 | opc: ld2r 42 | opc: ld2w 43 | opc: ld3 44 | opc: ld3b 45 | opc: ld3d 46 | opc: ld3h 47 | opc: ld3r 48 | opc: ld3w 49 | opc: ld4 50 | opc: ld4b 51 | opc: ld4d 52 | opc: ld4h 53 | opc: ld4r 54 | opc: ld4w 55 | opc: ldadd 56 | opc: ldadda 57 | opc: ldaddab 58 | opc: ldaddah 59 | opc: ldaddal 60 | opc: ldaddalb 61 | opc: ldaddalh 62 | opc: ldaddb 63 | opc: ldaddh 64 | opc: ldaddl 65 | opc: ldaddlb 66 | opc: ldaddlh 67 | opc: ldapr 68 | opc: ldaprb 69 | opc: ldaprh 70 | opc: ldapur 71 | opc: ldapurb 72 | opc: ldapurh 73 | opc: ldapursb 74 | opc: ldapursh 75 | opc: ldapursw 76 | opc: ldar 77 | opc: ldarb 78 | opc: ldarh 79 | opc: ldaxp 80 | opc: ldaxr 81 | opc: ldaxrb 82 | opc: ldaxrh 83 | opc: ldclr 84 | opc: ldclra 85 | opc: ldclrab 86 | opc: ldclrah 87 | opc: ldclral 88 | opc: ldclralb 89 | opc: ldclralh 90 | opc: ldclrb 91 | opc: ldclrh 92 | opc: ldclrl 93 | opc: ldclrlb 94 | opc: ldclrlh 95 | opc: ldeor 96 | opc: ldeora 97 | opc: ldeorab 98 | opc: ldeorah 99 | opc: ldeoral 100 | opc: ldeoralb 101 | opc: ldeoralh 102 | opc: ldeorb 103 | opc: ldeorh 104 | opc: ldeorl 105 | opc: ldeorlb 106 | opc: ldeorlh 107 | opc: ldff1b 108 | opc: ldff1d 109 | opc: ldff1h 110 | opc: ldff1sb 111 | opc: ldff1sh 112 | opc: ldff1sw 113 | opc: ldff1w 114 | opc: ldlar 115 | opc: ldlarb 116 | opc: ldlarh 117 | opc: ldnf1b 118 | opc: ldnf1d 119 | opc: ldnf1h 120 | opc: ldnf1sb 121 | opc: ldnf1sh 122 | opc: ldnf1sw 123 | opc: ldnf1w 124 | opc: ldnp 125 | opc: ldnt1b 126 | opc: ldnt1d 127 | opc: ldnt1h 128 | opc: ldnt1w 129 | opc: ldp 130 | opc: ldpsw 131 | opc: ldr 132 | opc: ldraa 133 | opc: ldrab 134 | opc: ldrb 135 | opc: ldrh 136 | opc: ldrsb 137 | opc: ldrsh 138 | opc: ldrsw 139 | opc: ldset 140 | opc: ldseta 141 | opc: ldsetab 142 | opc: ldsetah 143 | opc: ldsetal 144 | opc: ldsetalb 145 | opc: ldsetalh 146 | opc: ldsetb 147 | opc: ldseth 148 | opc: ldsetl 149 | opc: ldsetlb 150 | opc: ldsetlh 151 | opc: ldsmax 152 | opc: ldsmaxa 153 | opc: ldsmaxab 154 | opc: ldsmaxah 155 | opc: ldsmaxal 156 | opc: ldsmaxalb 157 | opc: ldsmaxalh 158 | opc: ldsmaxb 159 | opc: ldsmaxh 160 | opc: ldsmaxl 161 | opc: ldsmaxlb 162 | opc: ldsmaxlh 163 | opc: ldsmin 164 | opc: ldsmina 165 | opc: ldsminab 166 | opc: ldsminah 167 | opc: ldsminal 168 | opc: ldsminalb 169 | opc: ldsminalh 170 | opc: ldsminb 171 | opc: ldsminh 172 | opc: ldsminl 173 | opc: ldsminlb 174 | opc: ldsminlh 175 | opc: ldtr 176 | opc: ldtrb 177 | opc: ldtrh 178 | opc: ldtrsb 179 | opc: ldtrsh 180 | opc: ldtrsw 181 | opc: ldumax 182 | opc: ldumaxa 183 | opc: ldumaxab 184 | opc: ldumaxah 185 | opc: ldumaxal 186 | opc: ldumaxalb 187 | opc: ldumaxalh 188 | opc: ldumaxb 189 | opc: ldumaxh 190 | opc: ldumaxl 191 | opc: ldumaxlb 192 | opc: ldumaxlh 193 | opc: ldumin 194 | opc: ldumina 195 | opc: lduminab 196 | opc: lduminah 197 | opc: lduminal 198 | opc: lduminalb 199 | opc: lduminalh 200 | opc: lduminb 201 | opc: lduminh 202 | opc: lduminl 203 | opc: lduminlb 204 | opc: lduminlh 205 | opc: ldur 206 | opc: ldurb 207 | opc: ldurh 208 | opc: ldursb 209 | opc: ldursh 210 | opc: ldursw 211 | opc: ldxp 212 | opc: ldxr 213 | opc: ldxrb 214 | opc: ldxrh 215 | opc: prfb 216 | opc: prfd 217 | opc: prfh 218 | opc: prfm 219 | opc: prfum 220 | opc: prfw 221 | opc: st1 222 | opc: st1b 223 | opc: st1d 224 | opc: st1h 225 | opc: st1w 226 | opc: st2 227 | opc: st2b 228 | opc: st2d 229 | opc: st2h 230 | opc: st2w 231 | opc: st3 232 | opc: st3b 233 | opc: st3d 234 | opc: st3h 235 | opc: st3w 236 | opc: st4 237 | opc: st4b 238 | opc: st4d 239 | opc: st4h 240 | opc: st4w 241 | opc: stadd 242 | opc: staddb 243 | opc: staddh 244 | opc: staddl 245 | opc: staddlb 246 | opc: staddlh 247 | opc: stclr 248 | opc: stclrb 249 | opc: stclrh 250 | opc: stclrl 251 | opc: stclrlb 252 | opc: stclrlh 253 | opc: steor 254 | opc: steorb 255 | opc: steorh 256 | opc: steorl 257 | opc: steorlb 258 | opc: steorlh 259 | opc: stllr 260 | opc: stllrb 261 | opc: stllrh 262 | opc: stlr 263 | opc: stlrb 264 | opc: stlrh 265 | opc: stlur 266 | opc: stlurb 267 | opc: stlurh 268 | opc: stlxp 269 | opc: stlxr 270 | opc: stlxrb 271 | opc: stlxrh 272 | opc: stnp 273 | opc: stnt1b 274 | opc: stnt1d 275 | opc: stnt1h 276 | opc: stnt1w 277 | opc: stp 278 | opc: str 279 | opc: strb 280 | opc: strh 281 | opc: stset 282 | opc: stsetb 283 | opc: stseth 284 | opc: stsetl 285 | opc: stsetlb 286 | opc: stsetlh 287 | opc: stsmax 288 | opc: stsmaxb 289 | opc: stsmaxh 290 | opc: stsmaxl 291 | opc: stsmaxlb 292 | opc: stsmaxlh 293 | opc: stsmin 294 | opc: stsminb 295 | opc: stsminh 296 | opc: stsminl 297 | opc: stsminlb 298 | opc: stsminlh 299 | opc: sttr 300 | opc: sttrb 301 | opc: sttrh 302 | opc: stumax 303 | opc: stumaxb 304 | opc: stumaxh 305 | opc: stumaxl 306 | opc: stumaxlb 307 | opc: stumaxlh 308 | opc: stumin 309 | opc: stuminb 310 | opc: stuminh 311 | opc: stuminl 312 | opc: stuminlb 313 | opc: stuminlh 314 | opc: stur 315 | opc: sturb 316 | opc: sturh 317 | opc: stxp 318 | opc: stxr 319 | opc: stxrb 320 | opc: stxrh 321 | opc: swp 322 | opc: swpa 323 | opc: swpab 324 | opc: swpah 325 | opc: swpal 326 | opc: swpalb 327 | opc: swpalh 328 | opc: swpb 329 | opc: swph 330 | opc: swpl 331 | opc: swplb 332 | opc: swplh 333 | -------------------------------------------------------------------------------- /docs/atomic-bits-alt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jevinskie/xnu-trace/f6f0d03440fc381246a13ccaae75f67d6ffa0fac/docs/atomic-bits-alt.png -------------------------------------------------------------------------------- /docs/atomic-bits.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jevinskie/xnu-trace/f6f0d03440fc381246a13ccaae75f67d6ffa0fac/docs/atomic-bits.png -------------------------------------------------------------------------------- /docs/dyld-ventura-vmmap-after.txt: -------------------------------------------------------------------------------- 1 | 0x0000000104e38000-0x0000000104e9c000 R-X 0x64000 2 | 0x0000000104e9c000-0x0000000104ea0000 R-- 0x4000 3 | 0x0000000104ea0000-0x0000000104ea4000 RW- 0x4000 4 | 0x0000000104ea4000-0x0000000104fd0000 R-- 0x12c000 5 | 0x0000000104fd0000-0x0000000104fd4000 R-- 0x4000 6 | 0x0000000104fd4000-0x0000000104fd8000 R-- 0x4000 7 | 0x0000000104fd8000-0x0000000105018000 R-- 0x40000 8 | 0x0000000105058000-0x000000010505c000 R-- 0x4000 9 | 0x000000010505c000-0x0000000105064000 RW- 0x8000 10 | 0x0000000105064000-0x0000000105068000 R-- 0x4000 11 | 0x0000000105068000-0x000000010506c000 R-- 0x4000 12 | 0x000000010506c000-0x0000000105070000 RW- 0x4000 13 | 0x0000000105070000-0x0000000105074000 --- 0x4000 14 | 0x0000000105074000-0x000000010507c000 RW- 0x8000 15 | 0x000000010507c000-0x0000000105080000 --- 0x4000 16 | 0x0000000105080000-0x0000000105084000 --- 0x4000 17 | 0x0000000105084000-0x000000010508c000 RW- 0x8000 18 | 0x000000010508c000-0x0000000105090000 --- 0x4000 19 | 0x0000000105090000-0x0000000105094000 --- 0x4000 20 | 0x0000000105094000-0x000000010509c000 RW- 0x8000 21 | 0x000000010509c000-0x00000001050a0000 --- 0x4000 22 | 0x00000001050a0000-0x00000001050a4000 R-- 0x4000 23 | 0x00000001050a4000-0x00000001050a8000 R-- 0x4000 24 | 0x00000001050a8000-0x00000001050ac000 RW- 0x4000 25 | 0x000000015a600000-0x000000015a700000 RW- 0x100000 26 | 0x000000015a800000-0x000000015b000000 RW- 0x800000 27 | 0x0000000166fc8000-0x000000016a7cc000 --- 0x3804000 28 | 0x000000016a7cc000-0x000000016afc8000 RW- 0x7fc000 29 | 0x0000000180000000-0x00000001fc000000 R-- 0x7c000000 30 | 0x00000001a36cc000-0x00000001f7cec000 R-X 0x54620000 31 | 0x00000001f7cec000-0x00000001fae30000 R-- 0x3144000 32 | 0x00000001fce30000-0x00000001fce54000 RW- 0x24000 33 | 0x00000001fce54000-0x00000001fe000000 R-- 0x11ac000 34 | 0x00000001fce54000-0x00000001fce58000 RW- 0x4000 35 | 0x00000001fce58000-0x00000001fe000000 RW- 0x11a8000 36 | 0x00000001fe000000-0x00000001fefd8000 RW- 0xfd8000 37 | 0x00000001fefd8000-0x0000000200aa4000 RW- 0x1acc000 38 | 0x0000000200aa4000-0x0000000204004000 R-- 0x3560000 39 | 0x0000000204004000-0x0000000206000000 R-- 0x1ffc000 40 | 0x0000000206004000-0x0000000206b04000 R-- 0xb00000 41 | 0x0000000206b04000-0x000000023456c000 R-X 0x2da68000 42 | 0x000000023456c000-0x0000000235808000 R-- 0x129c000 43 | 0x0000000237808000-0x0000000238000000 RW- 0x7f8000 44 | 0x0000000238000000-0x00000002398ec000 RW- 0x18ec000 45 | 0x00000002398ec000-0x000000023aeb4000 RW- 0x15c8000 46 | 0x000000023aeb4000-0x000000023c334000 R-- 0x1480000 47 | 0x000000023c334000-0x000000023e000000 R-- 0x1ccc000 48 | 0x000000023e334000-0x000000026d998000 R-- 0x2f664000 49 | 0x000000026d998000-0x000000026d99c000 R-- 0x4000 50 | 0x0000000fc0000000-0x0000001000000000 --- 0x40000000 51 | 0x0000001000000000-0x0000007000000000 --- 0x6000000000 52 | 0x0000600000000000-0x0000600008000000 RW- 0x8000000 53 | 0x0000600008000000-0x0000600010000000 RW- 0x8000000 54 | 0x0000600010000000-0x0000600018000000 RW- 0x8000000 55 | 0x0000600018000000-0x0000600020000000 RW- 0x8000000 56 | 57 | img_info base: 0x104e38000 path: '/Users/jevin/code/apple/trace/xnu-single-step-trace/build/test/yield-loop-step-test' 58 | img_info base: 0x1a3724000 path: '/usr/lib/libobjc.A.dylib' 59 | img_info base: 0x1a376a000 path: '/usr/lib/dyld' 60 | img_info base: 0x1a37f5000 path: '/usr/lib/system/libsystem_blocks.dylib' 61 | img_info base: 0x1a37fa000 path: '/usr/lib/system/libxpc.dylib' 62 | img_info base: 0x1a383d000 path: '/usr/lib/system/libsystem_trace.dylib' 63 | img_info base: 0x1a3857000 path: '/usr/lib/system/libcorecrypto.dylib' 64 | img_info base: 0x1a38df000 path: '/usr/lib/system/libsystem_malloc.dylib' 65 | img_info base: 0x1a390b000 path: '/usr/lib/system/libdispatch.dylib' 66 | img_info base: 0x1a3953000 path: '/usr/lib/system/libsystem_featureflags.dylib' 67 | img_info base: 0x1a3956000 path: '/usr/lib/system/libsystem_c.dylib' 68 | img_info base: 0x1a39d7000 path: '/usr/lib/libc++.1.dylib' 69 | img_info base: 0x1a3a3e000 path: '/usr/lib/libc++abi.dylib' 70 | img_info base: 0x1a3a56000 path: '/usr/lib/system/libsystem_kernel.dylib' 71 | img_info base: 0x1a3a8f000 path: '/usr/lib/system/libsystem_pthread.dylib' 72 | img_info base: 0x1a3a9c000 path: '/usr/lib/system/libdyld.dylib' 73 | img_info base: 0x1a3ac0000 path: '/usr/lib/system/libsystem_platform.dylib' 74 | img_info base: 0x1a3ac8000 path: '/usr/lib/system/libsystem_info.dylib' 75 | img_info base: 0x1a68b6000 path: '/usr/lib/system/libsystem_darwin.dylib' 76 | img_info base: 0x1a6d1f000 path: '/usr/lib/system/libsystem_notify.dylib' 77 | img_info base: 0x1a8685000 path: '/usr/lib/system/libsystem_networkextension.dylib' 78 | img_info base: 0x1a8704000 path: '/usr/lib/system/libsystem_asl.dylib' 79 | img_info base: 0x1a9ec5000 path: '/usr/lib/system/libsystem_symptoms.dylib' 80 | img_info base: 0x1acc95000 path: '/usr/lib/system/libsystem_containermanager.dylib' 81 | img_info base: 0x1adcc5000 path: '/usr/lib/system/libsystem_configuration.dylib' 82 | img_info base: 0x1adcca000 path: '/usr/lib/system/libsystem_sandbox.dylib' 83 | img_info base: 0x1ae790000 path: '/usr/lib/system/libquarantine.dylib' 84 | img_info base: 0x1aedbf000 path: '/usr/lib/system/libsystem_coreservices.dylib' 85 | img_info base: 0x1af073000 path: '/usr/lib/system/libsystem_m.dylib' 86 | img_info base: 0x1af0ac000 path: '/usr/lib/system/libmacho.dylib' 87 | img_info base: 0x1af0ce000 path: '/usr/lib/system/libcommonCrypto.dylib' 88 | img_info base: 0x1af0df000 path: '/usr/lib/system/libunwind.dylib' 89 | img_info base: 0x1af0ea000 path: '/usr/lib/liboah.dylib' 90 | img_info base: 0x1af0f2000 path: '/usr/lib/system/libcopyfile.dylib' 91 | img_info base: 0x1af0fc000 path: '/usr/lib/system/libcompiler_rt.dylib' 92 | img_info base: 0x1af100000 path: '/usr/lib/system/libsystem_collections.dylib' 93 | img_info base: 0x1af105000 path: '/usr/lib/system/libsystem_secinit.dylib' 94 | img_info base: 0x1af108000 path: '/usr/lib/system/libremovefile.dylib' 95 | img_info base: 0x1af10b000 path: '/usr/lib/system/libkeymgr.dylib' 96 | img_info base: 0x1af10c000 path: '/usr/lib/system/libsystem_dnssd.dylib' 97 | img_info base: 0x1af115000 path: '/usr/lib/system/libcache.dylib' 98 | img_info base: 0x1af11b000 path: '/usr/lib/libSystem.B.dylib' 99 | -------------------------------------------------------------------------------- /docs/dyld-ventura-vmmap-before.txt: -------------------------------------------------------------------------------- 1 | 0x0000000104e38000-0x0000000104e9c000 R-X 0x64000 2 | 0x0000000104e9c000-0x0000000104ea0000 RW- 0x4000 3 | 0x0000000104ea0000-0x0000000104ea4000 RW- 0x4000 4 | 0x0000000104ea4000-0x0000000104fd0000 R-- 0x12c000 5 | 0x000000010514c000-0x00000001051d8000 R-X 0x8c000 6 | 0x00000001051d8000-0x00000001051e0000 RW- 0x8000 7 | 0x00000001051e0000-0x00000001051e4000 RW- 0x4000 8 | 0x00000001051e4000-0x00000001051e8000 RW- 0x4000 9 | 0x00000001051e8000-0x000000010523c000 R-- 0x54000 10 | 0x0000000166fc8000-0x000000016a7cc000 --- 0x3804000 11 | 0x000000016a7cc000-0x000000016afc8000 RW- 0x7fc000 12 | 0x0000000180000000-0x0000000280000000 R-- 0x100000000 13 | 0x00000001a36cc000-0x00000001f7cec000 R-X 0x54620000 14 | 0x00000001f7cec000-0x00000001fae30000 R-- 0x3144000 15 | 0x00000001fce30000-0x00000001fce54000 RW- 0x24000 16 | 0x00000001fce54000-0x00000001fce58000 RW- 0x4000 17 | 0x00000001fce58000-0x00000001fe000000 RW- 0x11a8000 18 | 0x00000001fe000000-0x00000001fefd8000 RW- 0xfd8000 19 | 0x00000001fefd8000-0x0000000200aa4000 RW- 0x1acc000 20 | 0x0000000200aa4000-0x0000000204004000 R-- 0x3560000 21 | 0x0000000206004000-0x0000000206b04000 R-- 0xb00000 22 | 0x0000000206b04000-0x000000023456c000 R-X 0x2da68000 23 | 0x000000023456c000-0x0000000235808000 R-- 0x129c000 24 | 0x0000000237808000-0x0000000238000000 RW- 0x7f8000 25 | 0x0000000238000000-0x00000002398ec000 RW- 0x18ec000 26 | 0x00000002398ec000-0x000000023aeb4000 RW- 0x15c8000 27 | 0x000000023aeb4000-0x000000023c334000 R-- 0x1480000 28 | 0x000000023e334000-0x000000026d998000 R-- 0x2f664000 29 | 0x000000026d998000-0x000000026d99c000 R-- 0x4000 30 | 0x0000000fc0000000-0x0000001000000000 --- 0x40000000 31 | 0x0000001000000000-0x0000007000000000 --- 0x6000000000 32 | -------------------------------------------------------------------------------- /docs/getbits.md: -------------------------------------------------------------------------------- 1 | # getbits 2 | 3 | ```cpp 4 | template inline unsigned long getbits(unsigned long[] bits) { 5 | const unsigned bitsPerLong = sizeof(unsigned long) * CHAR_BIT; 6 | const unsigned int bitsToGet = m - n; 7 | BOOST_STATIC_ASSERT(bitsToGet < bitsPerLong); 8 | const unsigned mask = (1UL << bitsToGet) - 1; 9 | const size_t index0 = n / bitsPerLong; 10 | const size_t index1 = m / bitsPerLong; 11 | // Do the bits to extract straddle a boundary? 12 | if (index0 == index1) { 13 | return (bits[index0] >> (n % bitsPerLong)) & mask; 14 | } else { 15 | return ((bits[index0] >> (n % bitsPerLong)) + 16 | (bits[index1] << (bitsPerLong - (m % bitsPerLong)))) & 17 | mask; 18 | } 19 | } 20 | ``` 21 | -------------------------------------------------------------------------------- /docs/log_msg_hdr_gpr_bitfield.json: -------------------------------------------------------------------------------- 1 | {"reg":[ 2 | { "name": "gc0", "bits": 5}, 3 | { "name": "gc1", "bits": 5}, 4 | { "name": "gc2", "bits": 5}, 5 | { "name": "gc3", "bits": 5}, 6 | { "name": "gc4", "bits": 5}, 7 | { "name": "b", "bits": 1}, 8 | { "name": "s", "bits": 1}, 9 | { "name": "c", "bits": 1}, 10 | { "bits": 1}, 11 | { "name": "ngc", "bits": 3} 12 | ]} 13 | -------------------------------------------------------------------------------- /docs/log_msg_hdr_mem_bitfield.json: -------------------------------------------------------------------------------- 1 | {"reg":[ 2 | { "name": "sz0", "bits": 3}, 3 | { "name": "sz1", "bits": 3}, 4 | { "name": "sz2", "bits": 3}, 5 | { "name": "sz3", "bits": 3}, 6 | { "name": "sz4", "bits": 3}, 7 | { "name": "r0", "bits": 1}, 8 | { "name": "r1", "bits": 1}, 9 | { "name": "r2", "bits": 1}, 10 | { "name": "r3", "bits": 1}, 11 | { "name": "r4", "bits": 1}, 12 | { "name": "w0", "bits": 1}, 13 | { "name": "w1", "bits": 1}, 14 | { "name": "w2", "bits": 1}, 15 | { "name": "w3", "bits": 1}, 16 | { "name": "w4", "bits": 1}, 17 | { "bits": 4}, 18 | { "name": "nma", "bits": 3} 19 | ]} 20 | -------------------------------------------------------------------------------- /docs/notes.txt: -------------------------------------------------------------------------------- 1 | Single Stepping: 2 | 3 | thread_dbg[set_single_step]: set_single_step(thread_vic, true) 4 | 5 | thread_vic[0x100]: add x0, #42 6 | 7 | thread_dbg[exc_raise]: set_single_step(thread_vic, false) 8 | 9 | thread_vic[0x100]: add x0, #42 10 | thread_vic[0x104]: add x0, #7 11 | 12 | // whoops, runaway thread. how to trap again after instruction finishes? 13 | 14 | 15 | TODO: 16 | -------------------------------------------------------------------------------- /docs/perf.txt: -------------------------------------------------------------------------------- 1 | MachORegions::lookup_inst 2 | 3 | benchmark loop body: 4 | *load instruction address ptr 5 | load instruction address 6 | *load m_nkeys 7 | hash instr address 8 | *load mph table ptr 9 | load salt value from mph table 10 | [hash again] 11 | *load buf ptr ptr 12 | load buf ptr 13 | load instr bytes 14 | 15 | 16 | header: 17 | load trace_inst_addrs[] data ptr from caller 18 | load m_salt_values[] data ptr from MinimalPerfectHash obj 19 | load m_nkeys from MinimalPerfectHash obj 20 | load m_page_bufs[] from MachORegions obj 21 | 22 | benchmark loop body: 23 | load inst_addr from trace_inst_addrs[] 24 | hmod = xxh3(inst_addr) 25 | load salt_value from m_salt_values[hmod] 26 | [potentially xxh3 again] 27 | load buf[] from m_page_bufs[salt_value] 28 | load buf[inst_addr & (PAGE_SZ-1)] 29 | -------------------------------------------------------------------------------- /docs/render-data-structures.json: -------------------------------------------------------------------------------- 1 | {reg:[ 2 | {name: 'addr', bits: 22}, 3 | {name: 'region', bits: 8}, 4 | {name: 'r', bits: 1}, 5 | {name: 'w', bits: 1} 6 | ]} 7 | -------------------------------------------------------------------------------- /docs/render-data-structures.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 02122293031addrregionrw 5 | -------------------------------------------------------------------------------- /docs/sleep-loop.txt: -------------------------------------------------------------------------------- 1 | 1: mov 1,281 ████████████████████████████████████████████████████████████████████████████████ 2 | 2: cmp 867 ██████████████████████████████████████████████████████▏ 3 | 3: ldr 717 ████████████████████████████████████████████▊ 4 | 4: add 695 ███████████████████████████████████████████▍ 5 | 5: b.ne 582 ████████████████████████████████████▎ 6 | 6: stp 582 ████████████████████████████████████▎ 7 | 7: ldp 555 ██████████████████████████████████▋ 8 | 8: ldrb 331 ████████████████████▋ 9 | 9: b.eq 280 █████████████████▍ 10 | 10: bl 248 ███████████████▍ 11 | 11: subs 232 ██████████████▍ 12 | 12: str 224 █████████████▉ 13 | 13: strb 179 ███████████▏ 14 | 14: and 178 ███████████ 15 | 15: sub 174 ██████████▊ 16 | 16: ret 167 ██████████▍ 17 | 17: b 154 █████████▌ 18 | 18: adrp 151 █████████▍ 19 | 19: pacibsp 130 ████████ 20 | 20: nop 128 ███████▉ 21 | 21: cbz 121 ███████▌ 22 | 22: retab 97 ██████ 23 | 23: csel 87 █████▍ 24 | 24: braa 84 █████▏ 25 | 25: tbnz 77 ████▊ 26 | 26: adr 75 ████▋ 27 | 27: orr 71 ████▍ 28 | 28: tbz 68 ████▏ 29 | 29: mrs 61 ███▊ 30 | 30: eor 60 ███▋ 31 | 31: movk 52 ███▏ 32 | 32: tst 47 ██▉ 33 | 33: ldur 45 ██▊ 34 | 34: ldrh 41 ██▌ 35 | 35: cset 39 ██▍ 36 | 36: b.hs 39 ██▍ 37 | 37: b.mi 38 ██▎ 38 | 38: stur 36 ██▏ 39 | 39: b.lo 35 ██▏ 40 | 40: cbnz 34 ██ 41 | 41: br 31 █▉ 42 | 42: sxtw 30 █▊ 43 | 43: autibsp 29 █▊ 44 | 44: b.hi 27 █▋ 45 | 45: lsr 25 █▌ 46 | 46: b.gt 24 █▍ 47 | 47: uminv 21 █▎ 48 | 48: ands 20 █▏ 49 | 49: b.lt 19 █▏ 50 | 50: adds 16 ▉ 51 | 51: b.le 16 ▉ 52 | 52: fmov 16 ▉ 53 | 53: ldrsw 15 ▉ 54 | 54: ldurh 15 ▉ 55 | 55: ccmp 14 ▊ 56 | 56: bics 14 ▊ 57 | 57: blraaz 14 ▊ 58 | 58: svc 13 ▊ 59 | 59: mul 12 ▋ 60 | 60: clz 10 ▌ 61 | 61: casa 8 ▍ 62 | 62: bfi 8 ▍ 63 | 63: csinc 7 ▍ 64 | 64: casl 7 ▍ 65 | 65: cas 7 ▍ 66 | 66: strh 7 ▍ 67 | 67: dup 7 ▍ 68 | 68: cmhi 7 ▍ 69 | 69: ldrsh 7 ▍ 70 | 70: msub 6 ▎ 71 | 71: sturh 6 ▎ 72 | 72: lsl 5 ▎ 73 | 73: sxtb 5 ▎ 74 | 74: ldarh 5 ▎ 75 | 75: ldrsb 5 ▎ 76 | 76: umull 5 ▎ 77 | 77: movi 5 ▎ 78 | 78: ubfx 5 ▎ 79 | 79: sturb 1 80 | 80: ldurb 1 81 | -------------------------------------------------------------------------------- /docs/whitebox.txt: -------------------------------------------------------------------------------- 1 | 1: cmp 76,476,234 ████████████████████████████████████████████████████████████████████████████████ 2 | 2: and 67,256,519 ██████████████████████████████████████████████████████████████████████▎ 3 | 3: mov 56,063,401 ██████████████████████████████████████████████████████████▋ 4 | 4: add 43,159,764 █████████████████████████████████████████████▏ 5 | 5: orr 39,642,989 █████████████████████████████████████████▍ 6 | 6: b.eq 36,968,508 ██████████████████████████████████████▋ 7 | 7: sub 25,612,388 ██████████████████████████▊ 8 | 8: b.ne 13,892,619 ██████████████▌ 9 | 9: ldr 12,491,663 █████████████ 10 | 10: ubfx 12,464,922 █████████████ 11 | 11: b.hs 12,396,289 ████████████▉ 12 | 12: tbz 12,392,918 ████████████▉ 13 | 13: cas 12,386,816 ████████████▉ 14 | 14: b 12,172,766 ████████████▋ 15 | 15: mul 11,710,067 ████████████▏ 16 | 16: csel 11,699,078 ████████████▏ 17 | 17: cbz 11,443,408 ███████████▉ 18 | 18: eor 4,860,311 █████ 19 | 19: movk 3,130,822 ███▎ 20 | 20: cinc 1,078,427 █▏ 21 | 21: str 816,821 ▊ 22 | 22: casl 738,748 ▊ 23 | 23: sdiv 738,267 ▊ 24 | 24: ldrb 671,016 ▋ 25 | 25: cset 554,200 ▌ 26 | 26: ldrsw 292,459 ▎ 27 | 27: lsr 277,139 ▎ 28 | 28: br 275,677 ▎ 29 | 29: strb 266,690 ▎ 30 | 30: adr 181,271 ▏ 31 | 31: nop 178,786 ▏ 32 | 32: madd 160,028 ▏ 33 | 33: bfi 113,148 34 | 34: lsl 108,022 35 | 35: stur 89,828 36 | 36: bfxil 75,208 37 | 37: ldur 71,714 38 | 38: ldurb 66,150 39 | 39: tst 65,553 40 | 40: stp 34,644 41 | 41: ldp 34,159 42 | 42: neg 25,715 43 | 43: adrp 25,300 44 | 44: tbnz 15,637 45 | 45: bic 11,572 46 | 46: bl 10,743 47 | 47: b.hi 9,884 48 | 48: xpacd 9,463 49 | 49: ccmp 8,030 50 | 50: pacibsp 7,227 51 | 51: umull 6,988 52 | 52: ror 6,728 53 | 53: subs 6,568 54 | 54: msub 6,518 55 | 55: cbnz 6,483 56 | 56: b.lo 5,832 57 | 57: retab 5,766 58 | 58: ret 5,497 59 | 59: b.ls 5,150 60 | 60: autdb 5,074 61 | 61: braa 4,815 62 | 62: mvn 4,764 63 | 63: ubfiz 4,652 64 | 64: udiv 4,390 65 | 65: b.lt 3,336 66 | 66: ldrh 2,843 67 | 67: autda 2,671 68 | 68: extr 2,121 69 | 69: ldar 1,910 70 | 70: ands 1,718 71 | 71: mrs 1,716 72 | 72: autibsp 1,461 73 | 73: b.le 1,350 74 | 74: cmn 1,160 75 | 75: sxtw 988 76 | 76: b.gt 969 77 | 77: adds 966 78 | 78: strh 958 79 | 79: paciza 884 80 | 80: fmov 625 81 | 81: casa 609 82 | 82: uminv 601 83 | 83: aese 546 84 | 84: brab 523 85 | 85: aesmc 507 86 | 86: ldrsb 506 87 | 87: pacdb 465 88 | 88: b.ge 450 89 | 89: xpaci 396 90 | 90: ldrsh 369 91 | 91: ld1 315 92 | 92: braaz 312 93 | 93: blraaz 303 94 | 94: ldnp 279 95 | 95: csinc 268 96 | 96: casal 213 97 | 97: pacib 196 98 | 98: autiza 195 99 | 99: movi 188 100 | 100: dup 182 101 | 101: cmhi 181 102 | 102: pacda 159 103 | 103: csinv 153 104 | 104: b.mi 139 105 | 105: umulh 137 106 | 106: ldurh 131 107 | 107: swpl 131 108 | 108: autia 129 109 | 109: blraa 126 110 | 110: bics 126 111 | 111: stlr 115 112 | 112: msr 102 113 | 113: asr 102 114 | 114: rev 95 115 | 115: blr 92 116 | 116: umaddl 90 117 | 117: smaddl 80 118 | 118: ldseth 79 119 | 119: sturb 73 120 | 120: pacga 68 121 | 121: ldaddl 61 122 | 122: svc 58 123 | 123: autib 56 124 | 124: ldapr 53 125 | 125: clz 52 126 | 126: sturh 50 127 | 127: ldadd 43 128 | 128: rbit 43 129 | 129: ldursh 38 130 | 130: cmeq 33 131 | 131: dmb 32 132 | 132: st1 29 133 | 133: tbl 28 134 | 134: sbfx 27 135 | 135: bti 26 136 | 136: sxtb 24 137 | 137: ldaddal 18 138 | 138: ucvtf 18 139 | 139: adcs 17 140 | 140: pacia 16 141 | 141: ldset 16 142 | 142: ldpsw 12 143 | 143: eon 11 144 | 144: b.vs 10 145 | 145: sbfiz 10 146 | 146: fcmp 9 147 | 147: fmul 9 148 | 148: ext 9 149 | 149: csneg 8 150 | 150: fcsel 8 151 | 151: fcvtzu 6 152 | 152: smulh 6 153 | 153: swp 3 154 | 154: ldclr 2 155 | 155: cnt 1 156 | 156: ldursb 1 157 | 157: ccmn 1 158 | 158: caspl 1 159 | 159: ldaddh 1 160 | 160: fneg 1 161 | 161: uaddlv 1 162 | -------------------------------------------------------------------------------- /include/xnu-trace/ARM64Disassembler.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common.h" 4 | -------------------------------------------------------------------------------- /include/xnu-trace/ARM64InstrHistogram.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common.h" 4 | 5 | #include 6 | 7 | class XNUTRACE_EXPORT ARM64InstrHistogram { 8 | public: 9 | ARM64InstrHistogram(bool add_only = false); 10 | ARM64InstrHistogram operator+(const ARM64InstrHistogram &other) const; 11 | ARM64InstrHistogram operator+=(const ARM64InstrHistogram &other); 12 | 13 | XNUTRACE_INLINE void add(uint32_t instr); 14 | 15 | void print(int max_num = 64, unsigned int width = 80) const; 16 | 17 | private: 18 | std::vector m_instr_to_op_lut; 19 | std::vector m_op_count_lut; 20 | }; 21 | -------------------------------------------------------------------------------- /include/xnu-trace/Atomic.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common.h" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | template class AtomicWaiter { 10 | public: 11 | AtomicWaiter(T count) : m_cnt{count} {}; 12 | void release(T num = 1) { 13 | m_cnt -= num; 14 | m_cnt.notify_all(); 15 | } 16 | void wait() { 17 | T cur = m_cnt; 18 | while (cur) { 19 | m_cnt.wait(cur); 20 | cur = m_cnt; 21 | } 22 | } 23 | 24 | private: 25 | std::atomic m_cnt; 26 | }; 27 | 28 | namespace std { 29 | using atomic_uint128_t = std::atomic; 30 | using atomic_int128_t = std::atomic; 31 | } // namespace std 32 | -------------------------------------------------------------------------------- /include/xnu-trace/CompressedFile.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common.h" 4 | 5 | #undef NDEBUG 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | struct ZSTD_CCtx_s; 12 | struct ZSTD_DCtx_s; 13 | 14 | namespace jev::xnutrace::detail { 15 | 16 | class XNUTRACE_EXPORT CompressedFile { 17 | public: 18 | CompressedFile(const std::filesystem::path &path, bool read, size_t hdr_sz, uint64_t hdr_magic, 19 | const void *hdr = nullptr, int level = 3, bool verbose = false, 20 | int num_threads = 0); 21 | ~CompressedFile(); 22 | 23 | template const T &header() const { 24 | assert(sizeof(T) == m_hdr_buf.size()); 25 | return *(T *)m_hdr_buf.data(); 26 | }; 27 | template T &header() { 28 | return const_cast(std::as_const(*this).header()); 29 | }; 30 | const std::vector &header_buf() const; 31 | std::vector &header_buf() { 32 | return const_cast &>(std::as_const(*this).header_buf()); 33 | } 34 | 35 | std::vector read(); 36 | std::vector read(size_t size); 37 | void read(uint8_t *buf, size_t size); 38 | template 39 | requires POD 40 | T read() { 41 | T buf; 42 | read((uint8_t *)&buf, sizeof(T)); 43 | return buf; 44 | } 45 | 46 | XNUTRACE_INLINE void write(std::span buf); 47 | XNUTRACE_INLINE void write(const void *buf, size_t size); 48 | XNUTRACE_INLINE void write(const uint8_t *buf, size_t size); 49 | XNUTRACE_INLINE void write(const char *buf, size_t size); 50 | template 51 | requires POD 52 | XNUTRACE_INLINE void write(const T &buf) { 53 | write({(uint8_t *)&buf, sizeof(buf)}); 54 | } 55 | 56 | size_t decompressed_size() const; 57 | 58 | private: 59 | const std::filesystem::path m_path; 60 | FILE *m_fh{}; 61 | ZSTD_CCtx_s *m_comp_ctx{}; 62 | std::vector m_in_buf; 63 | std::vector m_out_buf; 64 | ZSTD_DCtx_s *m_decomp_ctx{}; 65 | bool m_is_read{}; 66 | bool m_verbose{}; 67 | std::vector m_hdr_buf; 68 | size_t m_decomp_size{}; 69 | uint64_t m_num_disk_ops{}; 70 | uint64_t m_num_zstd_ops{}; 71 | size_t m_hdr_sz{}; 72 | }; 73 | 74 | } // namespace jev::xnutrace::detail 75 | 76 | template 77 | class XNUTRACE_EXPORT CompressedFile : public jev::xnutrace::detail::CompressedFile { 78 | public: 79 | CompressedFile(const std::filesystem::path &path, bool read, const HeaderT *hdr = nullptr, 80 | int level = 3, bool verbose = false) 81 | : jev::xnutrace::detail::CompressedFile::CompressedFile{ 82 | path, read, sizeof(HeaderT), HeaderT::magic, hdr, level, verbose} {}; 83 | 84 | const HeaderT &header() const { 85 | return jev::xnutrace::detail::CompressedFile::header(); 86 | } 87 | HeaderT &header() { 88 | return const_cast(std::as_const(*this).header()); 89 | } 90 | }; 91 | 92 | class XNUTRACE_EXPORT CompressedFileRawRead : public jev::xnutrace::detail::CompressedFile { 93 | public: 94 | CompressedFileRawRead(const std::filesystem::path &path) 95 | : jev::xnutrace::detail::CompressedFile::CompressedFile{path, true, UINT64_MAX, 96 | UINT64_MAX} {}; 97 | }; 98 | -------------------------------------------------------------------------------- /include/xnu-trace/EliasFano.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common.h" 4 | 5 | #include "BitVector.h" 6 | 7 | #include 8 | #include 9 | 10 | template class XNUTRACE_EXPORT EliasFanoSequence { 11 | public: 12 | using T = typename BitVector::RT; 13 | 14 | template 15 | EliasFanoSequence(std::span sorted_seq) 16 | : m_n(sorted_seq.size()), m_m{sorted_seq[sorted_seq.size() - 1]}, m_cln(ceil(log2(m_n))), 17 | m_clm(ceil(log2(m_m))), m_clmdn(ceil(log2(m_m) - log2(m_n))) { 18 | static_assert(sizeof(IT) <= sizeof(T)); 19 | m_lo = BitVectorFactory(m_clmdn, m_n); 20 | } 21 | 22 | T size() const noexcept { 23 | return m_n; 24 | } 25 | T max() const noexcept { 26 | return m_m; 27 | } 28 | 29 | private: 30 | const T m_n; 31 | const T m_m; 32 | const uint8_t m_cln; 33 | const uint8_t m_clm; 34 | const uint8_t m_clmdn; 35 | std::unique_ptr> m_lo; 36 | }; 37 | -------------------------------------------------------------------------------- /include/xnu-trace/FridaStalker.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common.h" 4 | 5 | #include "MachORegions.h" 6 | #include "Symbols.h" 7 | #include "TraceLog.h" 8 | #include "VMRegions.h" 9 | 10 | #include 11 | #include 12 | 13 | struct _GumStalker; 14 | typedef _GumStalker GumStalker; 15 | struct _GumStalkerTransformer; 16 | typedef _GumStalkerTransformer GumStalkerTransformer; 17 | 18 | struct cs_insn; 19 | 20 | class XNUTRACE_EXPORT FridaStalker { 21 | public: 22 | FridaStalker(const std::string &log_dir_path, bool symbolicate, int compression_level, 23 | bool stream); 24 | ~FridaStalker(); 25 | void follow(); 26 | void follow(size_t thread_id); 27 | void unfollow(); 28 | void unfollow(size_t thread_id); 29 | XNUTRACE_INLINE TraceLog &logger(); 30 | 31 | private: 32 | struct CBCtx { 33 | TraceLog *logger; 34 | cs_insn *insn; 35 | }; 36 | 37 | void write_trace(); 38 | static void transform_cb(void *iterator, void *output, void *user_data); 39 | static void instruction_cb(void *context, void *user_data); 40 | 41 | GumStalker *m_stalker; 42 | GumStalkerTransformer *m_transformer; 43 | TraceLog m_log; 44 | MachORegions m_macho_regions; 45 | VMRegions m_vm_regions; 46 | std::unique_ptr m_symbols; 47 | CBCtx cb_ctx; 48 | }; 49 | -------------------------------------------------------------------------------- /include/xnu-trace/MachORegions.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common.h" 4 | 5 | #include "MinimalPerfectHash.h" 6 | #include "log_structs.h" 7 | #include "utils.h" 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | 15 | struct image_info { 16 | uint64_t base; 17 | uint64_t size; 18 | uint64_t slide; 19 | std::filesystem::path path; 20 | uint8_t uuid[16]; 21 | std::vector bytes; 22 | sha256_t digest; 23 | bool is_jit; 24 | auto operator<=>(const image_info &rhs) const { 25 | return base <=> rhs.base; 26 | } 27 | std::filesystem::path log_path() const; 28 | }; 29 | 30 | class XNUTRACE_EXPORT MachORegions { 31 | public: 32 | MachORegions(task_t target_task); 33 | MachORegions(const log_region *region_buf, uint64_t num_regions, 34 | std::map> ®ions_bytes); 35 | void reset(); 36 | const std::vector ®ions() const; 37 | XNUTRACE_INLINE const image_info &lookup(uint64_t addr) const; 38 | XNUTRACE_INLINE std::pair lookup_idx(uint64_t addr) const; 39 | XNUTRACE_INLINE uint32_t lookup_inst(uint64_t addr) const; 40 | const image_info &lookup(const std::string &image_name) const; 41 | void dump() const; 42 | 43 | private: 44 | void create_hash(); 45 | const task_t m_target_task{}; 46 | std::vector m_regions; 47 | std::vector m_regions_bufs; 48 | mph_map_static m_pa2buf; 49 | }; 50 | -------------------------------------------------------------------------------- /include/xnu-trace/MinimalPerfectHash.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common.h" 4 | 5 | #include 6 | #include 7 | 8 | struct xxhash_64 { 9 | using type = uint64_t; 10 | XNUTRACE_INLINE static type hash(uint64_t val, uint64_t seed = 0) noexcept; 11 | }; 12 | 13 | struct xxhash_32 { 14 | using type = uint32_t; 15 | XNUTRACE_INLINE static type hash(uint64_t val, uint64_t seed = 0) noexcept; 16 | }; 17 | 18 | struct xxhash3_64 { 19 | using type = uint64_t; 20 | XNUTRACE_INLINE static type hash(uint64_t val, uint64_t seed = 0) noexcept; 21 | }; 22 | 23 | struct xxhash3_32 { 24 | using type = uint32_t; 25 | XNUTRACE_INLINE static type hash(uint64_t val, uint64_t seed = 0) noexcept; 26 | }; 27 | 28 | struct jevhash_64 { 29 | using type = uint64_t; 30 | XNUTRACE_INLINE static type hash(uint64_t val, uint64_t seed = 0) noexcept; 31 | }; 32 | 33 | struct jevhash_32 { 34 | using type = uint32_t; 35 | XNUTRACE_INLINE static type hash(uint64_t val, uint64_t seed = 0) noexcept; 36 | }; 37 | 38 | template class XNUTRACE_EXPORT MinimalPerfectHash { 39 | public: 40 | void build(std::span keys); 41 | XNUTRACE_INLINE uint32_t operator()(KeyT key) const; 42 | void stats() const; 43 | 44 | private: 45 | XNUTRACE_INLINE uint32_t mod(typename Hasher::type n) const; 46 | 47 | std::vector m_salts; 48 | uint64_t m_fastmod_u32_M; 49 | uint32_t m_nkeys; 50 | }; 51 | 52 | template 53 | class XNUTRACE_EXPORT mph_map_static { 54 | public: 55 | mph_map_static() = default; 56 | mph_map_static(const std::vector> &key_vals) { 57 | build(key_vals); 58 | } 59 | 60 | void build(const std::vector> &key_vals) { 61 | std::vector keys(key_vals.size()); 62 | size_t i = 0; 63 | for (const auto &[k, v] : key_vals) { 64 | keys[i] = k; 65 | ++i; 66 | } 67 | m_mph.build(keys); 68 | m_values.resize(key_vals.size()); 69 | for (const auto &[k, v] : key_vals) { 70 | m_values[m_mph(k)] = v; 71 | } 72 | } 73 | 74 | XNUTRACE_INLINE const ValueT &operator[](KeyT key) const { 75 | return m_values[m_mph(key)]; 76 | } 77 | 78 | private: 79 | MinimalPerfectHash m_mph; 80 | std::vector m_values; 81 | }; 82 | 83 | template 84 | class XNUTRACE_EXPORT mph_map { 85 | public: 86 | XNUTRACE_INLINE ValueT &operator[](KeyT key) { 87 | if (XNUTRACE_UNLIKELY(m_key_vals.size() == 0)) { 88 | m_key_vals.emplace_back(std::make_pair(key, ValueT{})); 89 | m_mph.build(std::span{&key, 1}); 90 | return m_key_vals[0].second; 91 | } 92 | auto &[k, v] = m_key_vals[m_mph(key)]; 93 | if (XNUTRACE_LIKELY(k == key)) { 94 | return v; 95 | } 96 | m_key_vals.emplace_back(std::make_pair(key, ValueT{})); 97 | rebuild(); 98 | return m_key_vals[m_mph(key)].second; 99 | } 100 | 101 | bool contains(KeyT key) const { 102 | if (XNUTRACE_UNLIKELY(m_key_vals.size() == 0)) { 103 | return false; 104 | } 105 | return m_key_vals[m_mph(key)].first == key; 106 | } 107 | 108 | template 109 | std::pair try_emplace(const KeyT &key, Args &&...args) { 110 | if (XNUTRACE_UNLIKELY(contains(key))) { 111 | return {m_key_vals[m_mph(key)].second, false}; 112 | } 113 | m_key_vals.emplace_back( 114 | std::pair(std::piecewise_construct, std::forward_as_tuple(key), 115 | std::forward_as_tuple(std::forward(args)...))); 116 | rebuild(); 117 | return {m_key_vals[m_mph(key)].second, true}; 118 | } 119 | 120 | std::vector keys() const { 121 | std::vector res(m_key_vals.size()); 122 | size_t i = 0; 123 | for (const auto &[k, v] : m_key_vals) { 124 | res[i] = k; 125 | ++i; 126 | } 127 | return res; 128 | } 129 | 130 | std::pair *begin() { 131 | return &*m_key_vals.begin(); 132 | } 133 | const std::pair *begin() const { 134 | return cbegin(); 135 | } 136 | const std::pair *cbegin() const { 137 | return &*m_key_vals.cbegin(); 138 | } 139 | std::pair *end() { 140 | return &*m_key_vals.end(); 141 | } 142 | const std::pair *end() const { 143 | return cend(); 144 | } 145 | const std::pair *cend() const { 146 | return &*m_key_vals.cend(); 147 | } 148 | 149 | private: 150 | void rebuild() { 151 | m_mph.build(keys()); 152 | std::vector new_idx(m_key_vals.size()); 153 | size_t i = 0; 154 | for (const auto &[k, v] : m_key_vals) { 155 | new_idx[i] = m_mph(k); 156 | ++i; 157 | } 158 | permute(new_idx); 159 | } 160 | 161 | // everybody loves raymond https://devblogs.microsoft.com/oldnewthing/20170102-00/?p=95095 162 | void permute(std::vector &new_idx) { 163 | for (uint32_t i = 0; i < new_idx.size(); i++) { 164 | std::pair t{std::move(m_key_vals[i])}; 165 | auto current = i; 166 | while (i != new_idx[current]) { 167 | auto next = new_idx[current]; 168 | m_key_vals[current] = std::move(m_key_vals[next]); 169 | new_idx[current] = current; 170 | current = next; 171 | } 172 | m_key_vals[current] = std::move(t); 173 | new_idx[current] = current; 174 | } 175 | } 176 | 177 | MinimalPerfectHash m_mph; 178 | std::vector> m_key_vals; 179 | }; 180 | -------------------------------------------------------------------------------- /include/xnu-trace/RankSelect.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common.h" 4 | -------------------------------------------------------------------------------- /include/xnu-trace/Signpost.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common.h" 4 | 5 | #include "XNUCommpageTime.h" 6 | 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | constexpr auto SUBSYSTEM = "vin.je.xnutracer"; 13 | 14 | template 15 | concept StringU64Callback = requires(T cb) { 16 | { cb(uint64_t{}) } -> std::same_as; 17 | }; 18 | 19 | class XNUTRACE_EXPORT Signpost { 20 | public: 21 | Signpost(const std::string &category, const std::string &name, bool event = false, 22 | std::string msg = ""); 23 | void start(std::string msg = ""); 24 | void end(std::string msg = ""); 25 | template void end(T const &cb) { 26 | end(cb(xnu_commpage_time_atus_to_ns(xnu_commpage_time_atus() - m_start_atus))); 27 | }; 28 | 29 | private: 30 | os_log_t m_log; 31 | os_signpost_id_t m_id{OS_SIGNPOST_ID_NULL}; 32 | const std::string m_name; 33 | uint64_t m_start_atus; 34 | const bool m_event; 35 | bool m_disabled{false}; 36 | }; 37 | -------------------------------------------------------------------------------- /include/xnu-trace/Symbols.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common.h" 4 | 5 | #include "log_structs.h" 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | #include 14 | 15 | struct sym_info { 16 | uint64_t base; 17 | uint64_t size; 18 | std::string name; 19 | std::filesystem::path path; 20 | auto operator<=>(const sym_info &rhs) const { 21 | return base <=> rhs.base; 22 | } 23 | }; 24 | 25 | XNUTRACE_EXPORT std::vector get_symbols(task_t target_task); 26 | 27 | XNUTRACE_EXPORT std::vector 28 | get_symbols_in_intervals(const std::vector &syms, 29 | const lib_interval_tree::interval_tree_t &intervals); 30 | 31 | class XNUTRACE_EXPORT Symbols { 32 | public: 33 | Symbols(task_t target_task); 34 | Symbols(const log_sym *sym_buf, uint64_t num_syms); 35 | void reset(); 36 | const std::vector &syms() const; 37 | const sym_info *lookup(uint64_t addr) const; 38 | 39 | private: 40 | const task_t m_target_task{}; 41 | std::vector m_syms; 42 | }; 43 | -------------------------------------------------------------------------------- /include/xnu-trace/ThreadPool.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common.h" 4 | 5 | #include "Atomic.h" 6 | 7 | #include 8 | 9 | #include 10 | 11 | class XNUTraceThreadPool : public BS::thread_pool { 12 | public: 13 | void wait_on_n_tasks(size_t n, const auto &block) { 14 | AtomicWaiter waiter(n); 15 | for (size_t i = 0; i < n; ++i) { 16 | push_task([&, i] { 17 | block(i); 18 | waiter.release(); 19 | }); 20 | } 21 | waiter.wait(); 22 | } 23 | 24 | template > 26 | std::enable_if_t> 27 | parallelize_indexed_loop(const Idx1 first_index, const Idx2 index_after_last, const F &loop, 28 | const size_t num_blocks = 0) { 29 | BS::blocks blks(first_index, index_after_last, 30 | num_blocks ? num_blocks : get_thread_count()); 31 | if (!blks.get_total_size()) { 32 | return; 33 | } 34 | const auto num_blks = blks.get_num_blocks(); 35 | AtomicWaiter waiter(num_blks); 36 | for (size_t i = 0; i < num_blks; ++i) { 37 | push_task([&, i] { 38 | loop(i, blks.start(i), blks.end(i)); 39 | waiter.release(); 40 | }); 41 | } 42 | waiter.wait(); 43 | } 44 | 45 | template 46 | std::enable_if_t> 47 | parallelize_indexed_loop(const Idx index_after_last, const F &loop, 48 | const size_t num_blocks = 0) { 49 | parallelize_indexed_loop(0, index_after_last, loop, num_blocks); 50 | } 51 | }; 52 | 53 | extern XNUTraceThreadPool xnutrace_pool; 54 | -------------------------------------------------------------------------------- /include/xnu-trace/TraceLog.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common.h" 4 | 5 | #include "CompressedFile.h" 6 | #include "MachORegions.h" 7 | #include "MinimalPerfectHash.h" 8 | #include "Signpost.h" 9 | #include "Symbols.h" 10 | #include "log_structs.h" 11 | #include "mach.h" 12 | #include "utils.h" 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | #include 19 | 20 | #include 21 | 22 | struct cs_insn; 23 | 24 | struct bb_t { 25 | uint64_t pc; 26 | uint32_t sz; 27 | } __attribute__((packed)); 28 | 29 | class log_thread_buf { 30 | public: 31 | class ctx_iterator; 32 | class iterator { 33 | public: 34 | using iterator_category = std::forward_iterator_tag; 35 | using difference_type = std::ptrdiff_t; 36 | using value_type = const log_msg; 37 | using pointer = const log_msg *; 38 | using reference = const log_msg &; 39 | 40 | iterator(pointer ptr, pointer end) : m_ptr(ptr), m_end(end) {} 41 | 42 | reference operator*() const { 43 | return *m_ptr; 44 | } 45 | pointer operator->() { 46 | return m_ptr; 47 | } 48 | iterator &operator++() { 49 | m_ptr = (pointer)((uintptr_t)m_ptr + m_ptr->size()); 50 | if (XNUTRACE_LIKELY(m_ptr != m_end) && XNUTRACE_UNLIKELY(m_ptr->is_sync_frame())) { 51 | // sync frame guaranteed to be followed by non-sync 52 | m_ptr = (pointer)((uintptr_t)m_ptr + m_ptr->size()); 53 | } 54 | return *this; 55 | } 56 | iterator operator++(int) { 57 | iterator tmp = *this; 58 | ++(*this); 59 | return tmp; 60 | } 61 | friend bool operator==(const iterator &a, const iterator &b) { 62 | return a.m_ptr == b.m_ptr; 63 | }; 64 | friend bool operator!=(const iterator &a, const iterator &b) { 65 | return a.m_ptr != b.m_ptr; 66 | }; 67 | 68 | private: 69 | pointer m_ptr{}; 70 | pointer m_end{}; 71 | }; 72 | 73 | class ctx_iterator : public iterator { 74 | public: 75 | ctx_iterator(pointer ptr, pointer end, const log_arm64_cpu_context *ctx) 76 | : iterator(ptr, end) { 77 | if (ctx) { 78 | memcpy(&m_ctx, &ctx, sizeof(m_ctx)); 79 | } 80 | } 81 | ctx_iterator(iterator it, const log_arm64_cpu_context *ctx) : iterator(it) { 82 | if (ctx) { 83 | memcpy(&m_ctx, &ctx, sizeof(m_ctx)); 84 | } 85 | } 86 | iterator &operator++() { 87 | auto &res = iterator::operator++(); 88 | m_ctx.update(*res); 89 | return res; 90 | } 91 | const log_arm64_cpu_context &ctx() const { 92 | return m_ctx; 93 | } 94 | 95 | private: 96 | log_arm64_cpu_context m_ctx; 97 | }; 98 | 99 | class pc_iterator : public iterator { 100 | public: 101 | pc_iterator(pointer ptr, pointer end, uint64_t pc) : iterator(ptr, end), m_pc{pc} {} 102 | pc_iterator(iterator it, uint64_t pc) : iterator(it), m_pc{pc} {} 103 | iterator &operator++() { 104 | auto &res = iterator::operator++(); 105 | if (res->pc_branched()) { 106 | m_pc = res->pc(); 107 | } else { 108 | m_pc += 4; 109 | } 110 | return res; 111 | } 112 | uint64_t pc() const { 113 | return m_pc; 114 | } 115 | 116 | private: 117 | uint64_t m_pc; 118 | }; 119 | 120 | log_thread_buf() = default; 121 | log_thread_buf(const std::vector &&buf, uint64_t num_inst) 122 | : m_buf{buf}, m_num_inst{num_inst} {}; 123 | 124 | uint64_t num_inst() const { 125 | return m_num_inst; 126 | } 127 | uint64_t num_bytes() const { 128 | return m_buf.size(); 129 | } 130 | 131 | const log_msg &front() const { 132 | const auto &res = *(log_msg *)m_buf.data(); 133 | assert(res.is_sync_frame()); 134 | return res; 135 | } 136 | 137 | log_msg *pointer_begin() const { 138 | return (log_msg *)m_buf.data(); 139 | } 140 | log_msg *pointer_end() const { 141 | return (log_msg *)(m_buf.data() + m_buf.size()); 142 | } 143 | 144 | iterator begin() const { 145 | return iterator(pointer_begin(), pointer_end()); 146 | } 147 | iterator end() const { 148 | return iterator(pointer_end(), pointer_end()); 149 | } 150 | 151 | ctx_iterator ctx_begin() const { 152 | return ctx_iterator(pointer_begin(), pointer_end(), front().sync_ctx()); 153 | } 154 | ctx_iterator ctx_end() const { 155 | return ctx_iterator(pointer_end(), pointer_end(), nullptr); 156 | } 157 | 158 | pc_iterator pcs_begin() const { 159 | return pc_iterator(pointer_begin(), pointer_end(), front().sync_ctx()->pc); 160 | } 161 | pc_iterator pcs_end() const { 162 | return pc_iterator(pointer_end(), pointer_end(), 0); 163 | } 164 | 165 | std::vector chunk_into_bins(uint32_t n) { 166 | Signpost chunk_sp("log_thread_buf", "chunk_into_bins"); 167 | chunk_sp.start(); 168 | const auto raw_bins = 169 | chunk_into_bins_by_needle(n, m_buf.data(), m_buf.size(), log_msg::sync_frame_buf_hdr, 170 | sizeof(log_msg::sync_frame_buf_hdr)); 171 | chunk_sp.end(); 172 | return {}; 173 | } 174 | 175 | private: 176 | std::vector m_buf; 177 | uint64_t m_num_inst{}; 178 | }; 179 | 180 | static_assert(std::is_move_constructible_v, 181 | "log_thread_buf not move constructable"); 182 | static_assert(std::is_move_assignable_v, "log_thread_buf not move assignable"); 183 | 184 | XNUTRACE_EXPORT std::vector extract_bbs_from_pc_trace(const std::span &pcs); 185 | XNUTRACE_EXPORT std::vector extract_bbs_from_trace(const log_thread_buf &thread_buf); 186 | XNUTRACE_EXPORT std::vector extract_pcs_from_trace(const log_thread_buf &thread_buf); 187 | 188 | class XNUTRACE_EXPORT TraceLog { 189 | public: 190 | TraceLog(const std::string &log_dir_path, int compression_level, bool stream); 191 | TraceLog(const std::string &log_dir_path); 192 | XNUTRACE_INLINE void log(thread_t thread, uint64_t pc); 193 | XNUTRACE_INLINE void log(thread_t thread, const log_arm64_cpu_context *context, cs_insn *insn); 194 | void write(const MachORegions &macho_regions, const Symbols *symbols = nullptr); 195 | uint64_t num_inst() const; 196 | size_t num_bytes() const; 197 | const MachORegions &macho_regions() const; 198 | const Symbols &symbols() const; 199 | const std::map &parsed_logs() const; 200 | static constexpr uint32_t sync_every = 1024 * 1024; // 1 MB, overhead 0.09% per MB 201 | 202 | private: 203 | struct thread_ctx { 204 | std::vector log_buf; 205 | std::unique_ptr> log_stream; 206 | XNUTRACE_ALIGNED(16) log_arm64_cpu_context last_cpu_ctx; 207 | uint64_t num_inst{}; 208 | uint32_t sz_since_last_sync{sync_every + 1}; 209 | XNUTRACE_INLINE void write_log_msg(const log_arm64_cpu_context *ctx, cs_insn *insn); 210 | XNUTRACE_INLINE void write_log_msg(uint64_t pc); 211 | void write_sync(); 212 | }; 213 | uint64_t m_num_inst{}; 214 | std::unique_ptr m_macho_regions; 215 | std::unique_ptr m_symbols; 216 | std::map m_parsed_logs; 217 | std::filesystem::path m_log_dir_path; 218 | int m_compression_level{}; 219 | bool m_stream{}; 220 | mph_map m_thread_ctxs; 221 | }; 222 | -------------------------------------------------------------------------------- /include/xnu-trace/VMRegions.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common.h" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | struct region { 13 | uint64_t base; 14 | uint64_t size; 15 | uint64_t depth; 16 | std::optional path; 17 | vm_prot_t prot; 18 | uint32_t tag; 19 | bool submap; 20 | auto operator<=>(const region &rhs) const { 21 | return base <=> rhs.base; 22 | } 23 | }; 24 | 25 | XNUTRACE_EXPORT std::vector get_vm_regions(task_t target_task); 26 | 27 | class XNUTRACE_EXPORT VMRegions { 28 | public: 29 | VMRegions(task_t target_task); 30 | 31 | void reset(); 32 | 33 | private: 34 | const task_t m_target_task; 35 | std::vector m_all_regions; 36 | }; 37 | -------------------------------------------------------------------------------- /include/xnu-trace/XNUCommpageTime.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common.h" 4 | 5 | #include "utils.h" 6 | 7 | #include 8 | 9 | XNUTRACE_EXPORT XNUTRACE_INLINE uint64_t xnu_commpage_time_seconds(); 10 | XNUTRACE_EXPORT XNUTRACE_INLINE uint64_t xnu_commpage_time_atus(); 11 | XNUTRACE_EXPORT XNUTRACE_INLINE uint64_t xnu_commpage_time_atus_to_ns(uint64_t atus); 12 | 13 | template 14 | concept VoidVoidCallback = requires(T cb) { 15 | { cb() } -> std::same_as; 16 | }; 17 | 18 | static inline void xnu_fast_timeout_dummy_cb() {}; 19 | 20 | template 21 | class XNUTRACE_EXPORT XNUFastTimeout { 22 | public: 23 | XNUFastTimeout(uint64_t nanoseconds, T const &cb) : m_cb(cb) { 24 | mach_timebase_info_data_t tb_info; 25 | mach_check(mach_timebase_info(&tb_info), "XNUFastTimeout mach_timebase_info"); 26 | const auto num_atus = (double)nanoseconds * tb_info.denom / tb_info.numer; 27 | m_end_deadline = xnu_commpage_time_atus() + num_atus; 28 | }; 29 | XNUFastTimeout(uint64_t nanoseconds) 30 | : XNUFastTimeout(nanoseconds, xnu_fast_timeout_dummy_cb) {}; 31 | XNUTRACE_INLINE void check() const { 32 | if (XNUTRACE_UNLIKELY(xnu_commpage_time_atus() >= m_end_deadline)) { 33 | m_cb(); 34 | } 35 | }; 36 | XNUTRACE_INLINE bool done() const { 37 | return xnu_commpage_time_atus() >= m_end_deadline; 38 | }; 39 | 40 | private: 41 | uint64_t m_end_deadline; 42 | T const &m_cb; 43 | }; 44 | -------------------------------------------------------------------------------- /include/xnu-trace/XNUTracer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common.h" 4 | 5 | #include "MachORegions.h" 6 | #include "Symbols.h" 7 | #include "TraceLog.h" 8 | #include "VMRegions.h" 9 | 10 | #include 11 | 12 | #include 13 | 14 | class XNUTRACE_EXPORT XNUTracer { 15 | public: 16 | struct opts { 17 | std::string trace_path; 18 | bool symbolicate; 19 | int compression_level; 20 | bool stream; 21 | }; 22 | 23 | XNUTracer(task_t target_task, const opts &options); 24 | XNUTracer(pid_t target_pid, const opts &options); 25 | XNUTracer(std::string target_name, const opts &options); 26 | XNUTracer(std::vector spawn_args, bool pipe_ctrl, bool disable_aslr, 27 | const opts &options); 28 | ~XNUTracer(); 29 | 30 | void suspend(); 31 | void resume(); 32 | pid_t pid(); 33 | dispatch_source_t proc_dispath_source(); 34 | dispatch_source_t breakpoint_exception_port_dispath_source(); 35 | dispatch_source_t pipe_dispatch_source(); 36 | void set_single_step(bool do_single_step); 37 | XNUTRACE_INLINE TraceLog &logger(); 38 | double elapsed_time() const; 39 | uint64_t context_switch_count_self() const; 40 | uint64_t context_switch_count_target() const; 41 | void handle_pipe(); 42 | 43 | private: 44 | void setup_breakpoint_exception_handler(); 45 | void install_breakpoint_exception_handler(); 46 | void uninstall_breakpoint_exception_handler(); 47 | void setup_breakpoint_exception_port_dispath_source(); 48 | void setup_proc_dispath_source(); 49 | void setup_pipe_dispatch_source(); 50 | void start_measuring_stats(); 51 | void stop_measuring_stats(); 52 | pid_t spawn_with_args(const std::vector &spawn_args, bool pipe_ctrl, 53 | bool disable_aslr); 54 | void common_ctor(bool pipe_ctrl, bool was_spawned, bool symbolicate); 55 | 56 | private: 57 | task_t m_target_task{TASK_NULL}; 58 | mach_port_t m_breakpoint_exc_port{MACH_PORT_NULL}; 59 | mach_port_t m_orig_breakpoint_exc_port{MACH_PORT_NULL}; 60 | exception_behavior_t m_orig_breakpoint_exc_behavior{0}; 61 | thread_state_flavor_t m_orig_breakpoint_exc_flavor{0}; 62 | dispatch_queue_t m_queue{nullptr}; 63 | dispatch_source_t m_proc_source{nullptr}; 64 | dispatch_source_t m_breakpoint_exc_source{nullptr}; 65 | dispatch_source_t m_pipe_source{nullptr}; 66 | std::optional m_target2tracer_fd; 67 | std::optional m_tracer2target_fd; 68 | bool m_single_stepping{}; 69 | bool m_measuring_stats{}; 70 | TraceLog m_log; 71 | double m_elapsed_time{}; 72 | timespec m_start_time{}; 73 | uint64_t m_target_total_csw{}; 74 | int32_t m_target_start_num_csw{}; 75 | uint64_t m_self_total_csw{}; 76 | int32_t m_self_start_num_csw{}; 77 | std::unique_ptr m_macho_regions; 78 | std::unique_ptr m_vm_regions; 79 | std::unique_ptr m_symbols; 80 | }; 81 | 82 | extern XNUTracer *g_tracer; 83 | -------------------------------------------------------------------------------- /include/xnu-trace/common-c.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define XNUTRACE_EXPORT __attribute__((visibility("default"))) 4 | #define XNUTRACE_INLINE __attribute__((always_inline)) 5 | // #define XNUTRACE_INLINE 6 | #define XNUTRACE_NOINLINE __attribute__((noinline)) 7 | #define XNUTRACE_LIKELY(cond) __builtin_expect((cond), 1) 8 | #define XNUTRACE_UNLIKELY(cond) __builtin_expect((cond), 0) 9 | #define XNUTRACE_BREAK() __builtin_debugtrap() 10 | #define XNUTRACE_ALIGNED(n) __attribute__((aligned(n))) 11 | #define XNUTRACE_ASSUME_ALIGNED(ptr, n) __builtin_assume_aligned((ptr), n) 12 | #define XNUTRACE_UNREACHABLE() __builtin_unreachable() 13 | -------------------------------------------------------------------------------- /include/xnu-trace/common.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "common-c.h" 10 | 11 | using namespace std::string_literals; 12 | 13 | template consteval size_t sizeofbits() { 14 | return sizeof(T) * 8; 15 | } 16 | 17 | consteval size_t sizeofbits(const auto &o) { 18 | return sizeof(o) * 8; 19 | } 20 | 21 | template constexpr auto type_name() { 22 | std::string_view name, prefix, suffix; 23 | #ifdef __clang__ 24 | name = __PRETTY_FUNCTION__; 25 | prefix = "auto type_name() [T = "; 26 | suffix = "]"; 27 | #elif defined(__GNUC__) 28 | name = __PRETTY_FUNCTION__; 29 | prefix = "constexpr auto type_name() [with T = "; 30 | suffix = "]"; 31 | #elif defined(_MSC_VER) 32 | name = __FUNCSIG__; 33 | prefix = "auto __cdecl type_name<"; 34 | suffix = ">(void)"; 35 | #endif 36 | name.remove_prefix(prefix.size()); 37 | name.remove_suffix(suffix.size()); 38 | return name; 39 | } 40 | 41 | template 42 | concept POD = std::is_trivial_v && std::is_standard_layout_v; 43 | 44 | using uint128_t = __uint128_t; 45 | using int128_t = __int128_t; 46 | 47 | constexpr uint64_t PAGE_SZ = 4 * 1024; 48 | constexpr uint64_t PAGE_SZ_LOG2 = 12; 49 | constexpr uint64_t PAGE_SZ_MASK = 0xfff; 50 | 51 | template 52 | using uint_n = std::conditional_t< 53 | (NBits > 0 && NBits <= 8), uint8_t, 54 | std::conditional_t< 55 | (NBits > 8 && NBits <= 16), uint16_t, 56 | std::conditional_t<(NBits > 16 && NBits <= 32), uint32_t, 57 | std::conditional_t<(NBits > 32 && NBits <= 64), uint64_t, 58 | std::conditional_t<(NBits > 64 && NBits <= 128), 59 | uint128_t, void>>>>>; 60 | 61 | template 62 | using sint_n = std::conditional_t< 63 | (NBits > 0 && NBits <= 8), int8_t, 64 | std::conditional_t< 65 | (NBits > 8 && NBits <= 16), int16_t, 66 | std::conditional_t< 67 | (NBits > 16 && NBits <= 32), int32_t, 68 | std::conditional_t<(NBits > 32 && NBits <= 64), int64_t, 69 | std::conditional_t<(NBits > 64 && NBits <= 128), int128_t, void>>>>>; 70 | 71 | template 72 | using int_n = std::conditional_t, uint_n>; 73 | 74 | constexpr auto make_signed_v(auto val) { 75 | return std::make_signed_t(val); 76 | } 77 | -------------------------------------------------------------------------------- /include/xnu-trace/drcov.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common.h" 4 | 5 | struct drcov_bb_t { 6 | uint32_t mod_off; 7 | uint16_t sz; 8 | uint16_t mod_id; 9 | } __attribute__((packed)); 10 | -------------------------------------------------------------------------------- /include/xnu-trace/dyld.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common.h" 4 | 5 | #include "MachORegions.h" 6 | 7 | XNUTRACE_EXPORT std::vector get_dyld_image_infos(task_t target_task); 8 | -------------------------------------------------------------------------------- /include/xnu-trace/mach.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common.h" 4 | 5 | #include "log_structs.h" 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | XNUTRACE_EXPORT bool task_is_valid(task_t task); 14 | XNUTRACE_EXPORT pid_t pid_for_task(task_t task); 15 | XNUTRACE_EXPORT integer_t get_suspend_count(task_t task); 16 | 17 | XNUTRACE_EXPORT int64_t get_task_for_pid_count(task_t task); 18 | 19 | XNUTRACE_EXPORT std::vector read_target(task_t target_task, uint64_t target_addr, 20 | uint64_t sz); 21 | 22 | template 23 | XNUTRACE_EXPORT std::vector read_target(task_t target_task, const T *target_addr, 24 | uint64_t sz) { 25 | return read_target(target_task, (uint64_t)target_addr, sz); 26 | } 27 | 28 | XNUTRACE_EXPORT std::string read_cstr_target(task_t target_task, uint64_t target_addr); 29 | XNUTRACE_EXPORT std::string read_cstr_target(task_t target_task, const char *target_addr); 30 | 31 | XNUTRACE_EXPORT XNUTRACE_INLINE void set_single_step_thread(thread_t thread, bool do_ss); 32 | XNUTRACE_EXPORT void set_single_step_task(task_t task, bool do_ss); 33 | 34 | XNUTRACE_EXPORT XNUTRACE_INLINE void read_target_thread_cpu_context(thread_t thread_id, 35 | log_arm64_cpu_context *ctx); 36 | -------------------------------------------------------------------------------- /include/xnu-trace/macho.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common.h" 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | XNUTRACE_EXPORT std::vector read_macho_segs_target(task_t target_task, 11 | uint64_t macho_hdr_addr); 12 | XNUTRACE_EXPORT std::vector 13 | read_macho_segs_target(task_t target_task, const mach_header_64 *macho_hdr); 14 | 15 | XNUTRACE_EXPORT uint64_t get_text_size(const std::vector &segments); 16 | XNUTRACE_EXPORT uint64_t get_text_base(const std::vector &segments); 17 | -------------------------------------------------------------------------------- /include/xnu-trace/proc.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common.h" 4 | 5 | #include 6 | 7 | XNUTRACE_EXPORT pid_t pid_for_name(std::string process_name); 8 | XNUTRACE_EXPORT int32_t get_context_switch_count(pid_t pid); 9 | -------------------------------------------------------------------------------- /include/xnu-trace/utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common.h" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | #include 16 | 17 | #define MCA_BEGIN(name) \ 18 | do { \ 19 | __asm volatile("# LLVM-MCA-BEGIN " #name ::: "memory"); \ 20 | } while (0) 21 | #define MCA_END() \ 22 | do { \ 23 | __asm volatile("# LLVM-MCA-END" ::: "memory"); \ 24 | } while (0) 25 | 26 | struct mbedtls_sha256_context; 27 | 28 | using sha256_t = std::array; 29 | 30 | template size_t bytesizeof(const typename std::vector &vec) { 31 | return sizeof(T) * vec.size(); 32 | } 33 | 34 | template constexpr bool is_pow2(T num) { 35 | return std::popcount(num) == 1; 36 | } 37 | 38 | // behavior: 39 | // roundup_pow2_mul(16, 16) = 16 40 | // roundup_pow2_mul(17, 16) = 32 41 | template 42 | requires requires() { requires std::unsigned_integral; } 43 | constexpr U roundup_pow2_mul(U num, size_t pow2_mul) { 44 | const U mask = static_cast(pow2_mul) - 1; 45 | return (num + mask) & ~mask; 46 | } 47 | 48 | // behavior: 49 | // roundup_pow2_mul(16, 16) = 16 50 | // roundup_pow2_mul(17, 16) = 16 51 | template 52 | requires requires() { requires std::unsigned_integral; } 53 | constexpr U rounddown_pow2_mul(U num, size_t pow2_mul) { 54 | const U mask = static_cast(pow2_mul) - 1; 55 | return num & ~mask; 56 | } 57 | 58 | XNUTRACE_EXPORT void posix_check(int retval, const std::string &msg); 59 | XNUTRACE_EXPORT void mach_check(kern_return_t kr, const std::string &msg); 60 | 61 | XNUTRACE_EXPORT uint32_t get_num_cores(); 62 | 63 | XNUTRACE_EXPORT void hexdump(const void *data, size_t size); 64 | 65 | XNUTRACE_EXPORT std::vector read_file(const std::string &path); 66 | XNUTRACE_EXPORT void write_file(const std::string &path, const uint8_t *buf, size_t sz); 67 | 68 | XNUTRACE_EXPORT double timespec_diff(const timespec &a, const timespec &b); 69 | XNUTRACE_EXPORT std::string prot_to_str(vm_prot_t prot); 70 | XNUTRACE_EXPORT std::string block_str(double percentage, unsigned int width = 80); 71 | 72 | XNUTRACE_EXPORT sha256_t get_sha256(std::span buf); 73 | 74 | class XNUTRACE_EXPORT SHA256 { 75 | public: 76 | SHA256(); 77 | ~SHA256(); 78 | void update(std::span buf); 79 | sha256_t digest(); 80 | 81 | private: 82 | mbedtls_sha256_context *m_ctx; 83 | sha256_t m_digest; 84 | bool m_finished{}; 85 | }; 86 | 87 | XNUTRACE_EXPORT void *horspool_memmem(const void *haystack, size_t haystack_sz, const void *needle, 88 | size_t needle_sz); 89 | 90 | XNUTRACE_EXPORT std::vector chunk_into_bins_by_needle(uint32_t n, const void *haystack, 91 | size_t haystack_sz, 92 | const void *needle, size_t needle_sz); 93 | 94 | template std::vector read_numbers_from_file(const std::filesystem::path &path) { 95 | const auto buf = read_file(path); 96 | const auto raw_buf = (T *)buf.data(); 97 | const auto nnums = buf.size() / sizeof(T); 98 | std::vector nums; 99 | nums.reserve(nnums); 100 | for (size_t i = 0; i < nnums; ++i) { 101 | nums.emplace_back(raw_buf[i]); 102 | } 103 | return nums; 104 | } 105 | 106 | template std::vector get_random_scalars(size_t n) { 107 | std::vector res; 108 | res.reserve(n); 109 | for (size_t i = 0; i < n; ++i) { 110 | T r; 111 | arc4random_buf((uint8_t *)&r, sizeof(r)); 112 | res.emplace_back(r); 113 | } 114 | return res; 115 | } 116 | 117 | template 118 | std::vector get_random_sorted_unique_scalars(size_t min_sz, size_t max_sz) { 119 | std::vector res; 120 | do { 121 | res = std::move(get_random_scalars(max_sz)); 122 | std::sort(res.begin(), res.end()); 123 | res.erase(std::unique(res.begin(), res.end()), res.end()); 124 | } while (res.size() < min_sz); 125 | return res; 126 | } 127 | -------------------------------------------------------------------------------- /include/xnu-trace/xnu-trace-c.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "common-c.h" 7 | 8 | #ifdef __cplusplus 9 | extern "C" { 10 | #endif 11 | 12 | #define PIPE_TRACER2TARGET_FD (STDERR_FILENO + 1) 13 | #define PIPE_TARGET2TRACER_FD (STDERR_FILENO + 2) 14 | 15 | XNUTRACE_EXPORT void pipe_set_single_step(int do_ss); 16 | 17 | typedef void *stalker_t; 18 | 19 | XNUTRACE_EXPORT stalker_t create_stalker(const char *log_dir_path, int symbolicate, 20 | int compression_level, int stream); 21 | 22 | XNUTRACE_EXPORT void destroy_stalker(stalker_t stalker); 23 | 24 | XNUTRACE_EXPORT void stalker_follow_me(stalker_t stalker); 25 | 26 | XNUTRACE_EXPORT void stalker_follow_thread(stalker_t stalker, size_t thread_id); 27 | 28 | XNUTRACE_EXPORT void stalker_unfollow_me(stalker_t stalker); 29 | 30 | XNUTRACE_EXPORT void stalker_unfollow_thread(stalker_t stalker, size_t thread_id); 31 | 32 | #ifdef __cplusplus 33 | } 34 | #endif 35 | -------------------------------------------------------------------------------- /include/xnu-trace/xnu-trace.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "xnu-trace-c.h" 4 | 5 | #include "ARM64Disassembler.h" 6 | #include "ARM64InstrHistogram.h" 7 | #include "Atomic.h" 8 | #include "BitVector.h" 9 | #include "CompressedFile.h" 10 | #include "EliasFano.h" 11 | #include "FridaStalker.h" 12 | #include "MachORegions.h" 13 | #include "MinimalPerfectHash.h" 14 | #include "RankSelect.h" 15 | #include "Signpost.h" 16 | #include "Symbols.h" 17 | #include "ThreadPool.h" 18 | #include "TraceLog.h" 19 | #include "VMRegions.h" 20 | #include "XNUCommpageTime.h" 21 | #include "XNUTracer.h" 22 | #include "drcov.h" 23 | #include "dyld.h" 24 | #include "log_structs.h" 25 | #include "mach.h" 26 | #include "macho.h" 27 | #include "proc.h" 28 | #include "utils.h" 29 | -------------------------------------------------------------------------------- /jupyter-notebooks/atomic-bitvector-cairo.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "id": "df1c68bd", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "from drawing import cairo_context\n", 11 | "import cairo" 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": null, 17 | "id": "d7f98dac", 18 | "metadata": {}, 19 | "outputs": [], 20 | "source": [ 21 | "def demo():\n", 22 | " width, height = 800, 200\n", 23 | " with cairo_context(width, height, format=\"svg\") as ctx:\n", 24 | " ctx.scale(32, 32)\n", 25 | "# ctx.rectangle(4, 4, 8, 8)\n", 26 | "# ctx.fill()\n", 27 | " ctx.select_font_face(\"Arial\", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL)\n", 28 | " ctx.set_font_size(12)\n", 29 | " ctx.set_source_rgb(1, 0, 0)\n", 30 | " ctx.move_to(16, 16)\n", 31 | " ctx.show_text(\"243\")\n", 32 | " ctx.fill()\n", 33 | " return ctx" 34 | ] 35 | }, 36 | { 37 | "cell_type": "code", 38 | "execution_count": null, 39 | "id": "b5a462f3", 40 | "metadata": {}, 41 | "outputs": [ 42 | { 43 | "data": { 44 | "image/svg+xml": [ 45 | "\n", 46 | "\n", 47 | "\n", 48 | "\n", 49 | "\n" 50 | ], 51 | "text/plain": [ 52 | "" 53 | ] 54 | }, 55 | "execution_count": null, 56 | "metadata": {}, 57 | "output_type": "execute_result" 58 | } 59 | ], 60 | "source": [ 61 | "demo()" 62 | ] 63 | }, 64 | { 65 | "cell_type": "code", 66 | "execution_count": null, 67 | "id": "a9c77e1d", 68 | "metadata": {}, 69 | "outputs": [], 70 | "source": [] 71 | } 72 | ], 73 | "metadata": { 74 | "kernelspec": { 75 | "display_name": "Python 3 (ipykernel)", 76 | "language": "python", 77 | "name": "python3" 78 | } 79 | }, 80 | "nbformat": 4, 81 | "nbformat_minor": 5 82 | } 83 | -------------------------------------------------------------------------------- /jupyter-notebooks/drawing.py: -------------------------------------------------------------------------------- 1 | """Helpers for drawing in Jupyter notebooks with PyCairo.""" 2 | 3 | # https://github.com/nedbat/truchet/blob/main/drawing.py 4 | 5 | import contextlib 6 | import io 7 | import math 8 | import os.path 9 | 10 | import cairo 11 | import IPython.display 12 | 13 | # Compass points for making circle arcs 14 | DEG90 = math.pi / 2 15 | DEG180 = math.pi 16 | CE, CS, CW, CN = [i * DEG90 for i in range(4)] 17 | 18 | 19 | class _CairoContext: 20 | """Base class for Cairo contexts that can display in Jupyter, or write to a file.""" 21 | 22 | def __init__(self, width: int, height: int, output: str | None = None): 23 | self.width = width 24 | self.height = height 25 | if isinstance(output, str): 26 | self.output = os.path.expandvars(os.path.expanduser(output)) 27 | else: 28 | self.output = output 29 | self.surface = None 30 | self.ctx = None 31 | 32 | def _repr_pretty_(self, p, cycle_unused): 33 | """Plain text repr for the context.""" 34 | # This is implemented just to limit needless changes in notebook files. 35 | # This gets written to the .ipynb file, and the default includes the 36 | # memory address, which changes each time. This string does not. 37 | p.text(f"<{self.__class__.__module__}.{self.__class__.__name__}>") 38 | 39 | def _repr_html_(self): 40 | """ 41 | HTML display in Jupyter. 42 | 43 | If output went to a file, display a message saying so. If output 44 | didn't go to a file, do nothing and the derived class will implement a 45 | method to display the output in Jupyter. 46 | """ 47 | if self.output is not None: 48 | return f"Wrote to {self.output}" 49 | 50 | def __enter__(self): 51 | return self 52 | 53 | def __getattr__(self, name): 54 | """Proxy to the cairo context, so that we have all the same methods.""" 55 | return getattr(self.ctx, name) 56 | 57 | # Drawing helpers 58 | 59 | def circle(self, x, y, r): 60 | """Add a complete circle to the path.""" 61 | self.ctx.arc(x, y, r, 0, 2 * math.pi) 62 | 63 | @contextlib.contextmanager 64 | def save_restore(self): 65 | self.ctx.save() 66 | try: 67 | yield 68 | finally: 69 | self.ctx.restore() 70 | 71 | @contextlib.contextmanager 72 | def flip_lr(self, wh): 73 | with self.save_restore(): 74 | self.ctx.translate(wh, 0) 75 | self.ctx.scale(-1, 1) 76 | yield 77 | 78 | @contextlib.contextmanager 79 | def flip_tb(self, wh): 80 | with self.save_restore(): 81 | self.ctx.translate(0, wh) 82 | self.ctx.scale(1, -1) 83 | yield 84 | 85 | @contextlib.contextmanager 86 | def rotated(self, wh, nturns): 87 | with self.save_restore(): 88 | self.ctx.translate(wh / 2, wh / 2) 89 | self.ctx.rotate(math.pi * nturns / 2) 90 | self.ctx.translate(-wh / 2, -wh / 2) 91 | yield 92 | 93 | 94 | class _CairoSvg(_CairoContext): 95 | """For creating an SVG drawing in Jupyter.""" 96 | 97 | def __init__(self, width: int, height: int, output: str | None = None): 98 | super().__init__(width, height, output) 99 | self.svgio = io.BytesIO() 100 | self.surface = cairo.SVGSurface(self.svgio, self.width, self.height) 101 | self.surface.set_document_unit(cairo.SVGUnit.PX) 102 | self.ctx = cairo.Context(self.surface) 103 | 104 | def __exit__(self, typ, val, tb): 105 | self.surface.finish() 106 | if self.output is not None: 107 | with open(self.output, "wb") as svgout: 108 | svgout.write(self.svgio.getvalue()) 109 | 110 | def _repr_svg_(self): 111 | if self.output is None: 112 | return self.svgio.getvalue().decode() 113 | 114 | 115 | class _CairoPng(_CairoContext): 116 | """For creating a PNG drawing in Jupyter.""" 117 | 118 | def __init__(self, width: int, height: int, output: str | None = None): 119 | super().__init__(width, height, output) 120 | self.pngio = None 121 | self.surface = cairo.ImageSurface(cairo.Format.RGB24, self.width, self.height) 122 | self.ctx = cairo.Context(self.surface) 123 | 124 | def __exit__(self, typ, val, tb): 125 | if self.output is not None: 126 | self.surface.write_to_png(self.output) 127 | else: 128 | self.pngio = io.BytesIO() 129 | self.surface.write_to_png(self.pngio) 130 | self.surface.finish() 131 | 132 | def _repr_png_(self): 133 | if self.output is None: 134 | return self.pngio.getvalue() 135 | 136 | 137 | def cairo_context(width: int, height: int, format: str = "svg", output: str | None = None): 138 | """ 139 | Create a PyCairo context for use in Jupyter. 140 | 141 | Arguments: 142 | width (int), height (int): the size of the drawing in pixels. 143 | format (str): either "svg" or "png". 144 | output (optional str): if provided, the output will be written to this 145 | file. If None, the output will be displayed in the Jupyter notebook. 146 | 147 | Returns: 148 | A PyCairo context proxy. 149 | """ 150 | 151 | if format == "svg": 152 | cls = _CairoSvg 153 | elif format == "png": 154 | cls = _CairoPng 155 | else: 156 | raise ValueError(f"Unknown format: {format!r}") 157 | return cls(width, height, output) 158 | 159 | 160 | def svg_row(*svgs): 161 | sbs = '
{}
' 162 | return IPython.display.HTML(sbs.format("".join(s._repr_svg_() for s in svgs))) 163 | -------------------------------------------------------------------------------- /jupyter-notebooks/numba-tests.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "id": "72040ff0", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "import numba\n", 11 | "import numpy as np" 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": 6, 17 | "id": "b8ecd276", 18 | "metadata": {}, 19 | "outputs": [ 20 | { 21 | "name": "stdout", 22 | "output_type": "stream", 23 | "text": [ 24 | "[4642753818981826560]\n" 25 | ] 26 | }, 27 | { 28 | "data": { 29 | "text/plain": [ 30 | "array([4.64275382e+18, 2.43000000e+02])" 31 | ] 32 | }, 33 | "execution_count": 6, 34 | "metadata": {}, 35 | "output_type": "execute_result" 36 | } 37 | ], 38 | "source": [ 39 | "a = np.ndarray((1,), dtype=np.uint64)\n", 40 | "print(a)\n", 41 | "np.append(a, 243)" 42 | ] 43 | }, 44 | { 45 | "cell_type": "code", 46 | "execution_count": 7, 47 | "id": "050ad57a", 48 | "metadata": {}, 49 | "outputs": [], 50 | "source": [ 51 | "b = np.arange(0, 10)" 52 | ] 53 | }, 54 | { 55 | "cell_type": "code", 56 | "execution_count": 8, 57 | "id": "7aeec0a8", 58 | "metadata": {}, 59 | "outputs": [], 60 | "source": [ 61 | "c = np.arange(20, 30)" 62 | ] 63 | }, 64 | { 65 | "cell_type": "code", 66 | "execution_count": 9, 67 | "id": "4be8c9d8", 68 | "metadata": {}, 69 | "outputs": [ 70 | { 71 | "data": { 72 | "text/plain": [ 73 | "array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])" 74 | ] 75 | }, 76 | "execution_count": 9, 77 | "metadata": {}, 78 | "output_type": "execute_result" 79 | } 80 | ], 81 | "source": [ 82 | "b" 83 | ] 84 | }, 85 | { 86 | "cell_type": "code", 87 | "execution_count": 10, 88 | "id": "174b692e", 89 | "metadata": {}, 90 | "outputs": [ 91 | { 92 | "data": { 93 | "text/plain": [ 94 | "array([20, 21, 22, 23, 24, 25, 26, 27, 28, 29])" 95 | ] 96 | }, 97 | "execution_count": 10, 98 | "metadata": {}, 99 | "output_type": "execute_result" 100 | } 101 | ], 102 | "source": [ 103 | "c" 104 | ] 105 | }, 106 | { 107 | "cell_type": "code", 108 | "execution_count": 11, 109 | "id": "62e3e14f", 110 | "metadata": {}, 111 | "outputs": [ 112 | { 113 | "data": { 114 | "text/plain": [ 115 | "array([ 0, 21, 44, 69, 96, 125, 156, 189, 224, 261])" 116 | ] 117 | }, 118 | "execution_count": 11, 119 | "metadata": {}, 120 | "output_type": "execute_result" 121 | } 122 | ], 123 | "source": [ 124 | "b * c" 125 | ] 126 | }, 127 | { 128 | "cell_type": "code", 129 | "execution_count": 12, 130 | "id": "a3fc0272", 131 | "metadata": {}, 132 | "outputs": [ 133 | { 134 | "data": { 135 | "text/plain": [ 136 | "1185" 137 | ] 138 | }, 139 | "execution_count": 12, 140 | "metadata": {}, 141 | "output_type": "execute_result" 142 | } 143 | ], 144 | "source": [ 145 | "b.dot(c)" 146 | ] 147 | }, 148 | { 149 | "cell_type": "code", 150 | "execution_count": 13, 151 | "id": "6890dec1", 152 | "metadata": {}, 153 | "outputs": [ 154 | { 155 | "ename": "TypeError", 156 | "evalue": "only integer scalar arrays can be converted to a scalar index", 157 | "output_type": "error", 158 | "traceback": [ 159 | "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", 160 | "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", 161 | "Input \u001b[0;32mIn [13]\u001b[0m, in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mnp\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mconcatenate\u001b[49m\u001b[43m(\u001b[49m\u001b[43mb\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mc\u001b[49m\u001b[43m)\u001b[49m\n", 162 | "File \u001b[0;32m<__array_function__ internals>:180\u001b[0m, in \u001b[0;36mconcatenate\u001b[0;34m(*args, **kwargs)\u001b[0m\n", 163 | "\u001b[0;31mTypeError\u001b[0m: only integer scalar arrays can be converted to a scalar index" 164 | ] 165 | } 166 | ], 167 | "source": [ 168 | "np.concatenate(b, c)" 169 | ] 170 | }, 171 | { 172 | "cell_type": "code", 173 | "execution_count": 14, 174 | "id": "b3b5dd8e", 175 | "metadata": {}, 176 | "outputs": [ 177 | { 178 | "data": { 179 | "text/plain": [ 180 | "[array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]),\n", 181 | " array([20, 21, 22, 23, 24, 25, 26, 27, 28, 29])]" 182 | ] 183 | }, 184 | "execution_count": 14, 185 | "metadata": {}, 186 | "output_type": "execute_result" 187 | } 188 | ], 189 | "source": [ 190 | "[b, c]" 191 | ] 192 | }, 193 | { 194 | "cell_type": "code", 195 | "execution_count": 15, 196 | "id": "c5434cde", 197 | "metadata": {}, 198 | "outputs": [ 199 | { 200 | "data": { 201 | "text/plain": [ 202 | "array([[[ 0, 20],\n", 203 | " [ 1, 21],\n", 204 | " [ 2, 22],\n", 205 | " [ 3, 23],\n", 206 | " [ 4, 24],\n", 207 | " [ 5, 25],\n", 208 | " [ 6, 26],\n", 209 | " [ 7, 27],\n", 210 | " [ 8, 28],\n", 211 | " [ 9, 29]]])" 212 | ] 213 | }, 214 | "execution_count": 15, 215 | "metadata": {}, 216 | "output_type": "execute_result" 217 | } 218 | ], 219 | "source": [ 220 | "np.dstack((b, c))" 221 | ] 222 | }, 223 | { 224 | "cell_type": "code", 225 | "execution_count": null, 226 | "id": "c187dc79", 227 | "metadata": {}, 228 | "outputs": [], 229 | "source": [] 230 | } 231 | ], 232 | "metadata": { 233 | "kernelspec": { 234 | "display_name": "Python 3 (ipykernel)", 235 | "language": "python", 236 | "name": "python3" 237 | }, 238 | "language_info": { 239 | "codemirror_mode": { 240 | "name": "ipython", 241 | "version": 3 242 | }, 243 | "file_extension": ".py", 244 | "mimetype": "text/x-python", 245 | "name": "python", 246 | "nbconvert_exporter": "python", 247 | "pygments_lexer": "ipython3", 248 | "version": "3.10.6" 249 | } 250 | }, 251 | "nbformat": 4, 252 | "nbformat_minor": 5 253 | } 254 | -------------------------------------------------------------------------------- /jupyter-notebooks/transmorgifier.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "id": "1b7105b7", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "import numpy as np\n", 11 | "from xnutrace.tracelog import TraceLog" 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": null, 17 | "id": "0a9b4674", 18 | "metadata": {}, 19 | "outputs": [ 20 | { 21 | "name": "stdout", 22 | "output_type": "stream", 23 | "text": [ 24 | "'../../../build/yield-loop-step-test.bundle/thread-14851.bin' decompressed 2,846 bytes to 75,760 bytes\n" 25 | ] 26 | } 27 | ], 28 | "source": [ 29 | "tl = TraceLog(\"../../../build/yield-loop-step-test.bundle\")" 30 | ] 31 | }, 32 | { 33 | "cell_type": "code", 34 | "execution_count": null, 35 | "id": "f360febe", 36 | "metadata": {}, 37 | "outputs": [ 38 | { 39 | "data": { 40 | "text/plain": [ 41 | "[]" 42 | ] 43 | }, 44 | "execution_count": null, 45 | "metadata": {}, 46 | "output_type": "execute_result" 47 | } 48 | ], 49 | "source": [ 50 | "tl.subregions" 51 | ] 52 | }, 53 | { 54 | "cell_type": "code", 55 | "execution_count": null, 56 | "id": "700e092c", 57 | "metadata": {}, 58 | "outputs": [], 59 | "source": [ 60 | "def pow2_round_down(n: int, pow2: int) -> int:\n", 61 | " return n & ~(pow2 - np.uint8(1))" 62 | ] 63 | }, 64 | { 65 | "cell_type": "code", 66 | "execution_count": null, 67 | "id": "1833bfdc", 68 | "metadata": {}, 69 | "outputs": [], 70 | "source": [ 71 | "def pow2_round_up(n: int, pow2: int) -> int:\n", 72 | " return pow2_round_down(n, pow2) + pow2" 73 | ] 74 | }, 75 | { 76 | "cell_type": "code", 77 | "execution_count": null, 78 | "id": "7fafb029", 79 | "metadata": {}, 80 | "outputs": [ 81 | { 82 | "data": { 83 | "text/plain": [ 84 | "'0x8000'" 85 | ] 86 | }, 87 | "execution_count": null, 88 | "metadata": {}, 89 | "output_type": "execute_result" 90 | } 91 | ], 92 | "source": [ 93 | "hex(pow2_round_down(0x8FFF, 0x4000))" 94 | ] 95 | }, 96 | { 97 | "cell_type": "code", 98 | "execution_count": null, 99 | "id": "1c070f66", 100 | "metadata": {}, 101 | "outputs": [ 102 | { 103 | "data": { 104 | "text/plain": [ 105 | "'0xc000'" 106 | ] 107 | }, 108 | "execution_count": null, 109 | "metadata": {}, 110 | "output_type": "execute_result" 111 | } 112 | ], 113 | "source": [ 114 | "hex(pow2_round_up(0x8FFF, 0x4000))" 115 | ] 116 | }, 117 | { 118 | "cell_type": "code", 119 | "execution_count": null, 120 | "id": "04240a5d", 121 | "metadata": {}, 122 | "outputs": [], 123 | "source": [] 124 | } 125 | ], 126 | "metadata": { 127 | "kernelspec": { 128 | "display_name": "Python 3 (ipykernel)", 129 | "language": "python", 130 | "name": "python3" 131 | } 132 | }, 133 | "nbformat": 4, 134 | "nbformat_minor": 5 135 | } 136 | -------------------------------------------------------------------------------- /lib/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(xnu-trace) 2 | -------------------------------------------------------------------------------- /lib/xnu-trace/ARM64Disassembler.cpp: -------------------------------------------------------------------------------- 1 | #include "xnu-trace/ARM64Disassembler.h" 2 | #include "common-internal.h" 3 | -------------------------------------------------------------------------------- /lib/xnu-trace/ARM64InstrHistogram.cpp: -------------------------------------------------------------------------------- 1 | #include "xnu-trace/ARM64InstrHistogram.h" 2 | #include "common-internal.h" 3 | 4 | #include "xnu-trace/utils.h" 5 | 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | ARM64InstrHistogram::ARM64InstrHistogram(bool add_only) { 12 | m_op_count_lut.resize(UINT16_MAX); 13 | if (add_only) { 14 | return; 15 | } 16 | m_instr_to_op_lut.resize(1 << 22, UINT16_MAX); 17 | } 18 | 19 | ARM64InstrHistogram ARM64InstrHistogram::operator+(const ARM64InstrHistogram &other) const { 20 | ARM64InstrHistogram res(true); 21 | for (size_t i = 0; i < m_op_count_lut.size(); ++i) { 22 | res.m_op_count_lut[i] = m_op_count_lut[i] + other.m_op_count_lut[i]; 23 | } 24 | return res; 25 | } 26 | 27 | ARM64InstrHistogram ARM64InstrHistogram::operator+=(const ARM64InstrHistogram &other) { 28 | for (size_t i = 0; i < m_op_count_lut.size(); ++i) { 29 | m_op_count_lut[i] += other.m_op_count_lut[i]; 30 | } 31 | return *this; 32 | } 33 | 34 | void ARM64InstrHistogram::add(uint32_t instr) { 35 | auto op = m_instr_to_op_lut[instr >> 10]; 36 | if (op == UINT16_MAX) { 37 | Instruction inst_repr; 38 | assert(aarch64_decompose(instr, &inst_repr, 0) == DECODE_STATUS_OK); 39 | op = (uint16_t)inst_repr.operation; 40 | m_instr_to_op_lut[instr >> 10] = op; 41 | } 42 | ++m_op_count_lut[op]; 43 | } 44 | 45 | void ARM64InstrHistogram::print(int max_num, unsigned int width) const { 46 | std::map op_counts; 47 | size_t i = 0; 48 | for (const auto op_count : m_op_count_lut) { 49 | if (op_count) { 50 | op_counts.emplace(i, op_count); 51 | } 52 | ++i; 53 | } 54 | if (max_num < 0) { 55 | max_num = (int)op_counts.size(); 56 | } 57 | std::vector> sorted; 58 | for (const auto &it : op_counts) { 59 | sorted.emplace_back(it); 60 | } 61 | std::sort(sorted.begin(), sorted.end(), [](const auto &a, const auto &b) -> auto { 62 | return a.second > b.second; 63 | }); 64 | sorted.resize(max_num); 65 | const auto max_count = sorted[0].second; 66 | i = 1; 67 | for (const auto &[op, num] : sorted) { 68 | Instruction inst{.operation = (Operation)op}; 69 | fmt::print("{:s}\n", fmt::format(std::locale("en_US.UTF-8"), "{:5Ld}: {:8s} {:12Ld} {:s}", 70 | i, get_operation(&inst), num, 71 | block_str((double)num / max_count, width))); 72 | ++i; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /lib/xnu-trace/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_custom_command(OUTPUT 2 | ${CMAKE_CURRENT_BINARY_DIR}/mach_exc.h 3 | ${CMAKE_CURRENT_BINARY_DIR}/mach_excServer.c 4 | ${CMAKE_CURRENT_BINARY_DIR}/mach_excUser.c 5 | COMMAND mig ${CMAKE_CURRENT_SOURCE_DIR}/mach_exc.defs 6 | DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/mach_exc.defs 7 | ) 8 | 9 | add_library(xnu-trace-mach-exc STATIC ${CMAKE_CURRENT_BINARY_DIR}/mach_excServer.c ${CMAKE_CURRENT_BINARY_DIR}/mach_exc.h) 10 | target_compile_options(xnu-trace-mach-exc PRIVATE -Wall -Wextra -Wpedantic) 11 | 12 | target_include_directories(xnu-trace-mach-exc 13 | PRIVATE ${CMAKE_CURRENT_BINARY_DIR} 14 | ) 15 | 16 | 17 | set(INSTTRACE_SRC 18 | ARM64Disassembler.cpp 19 | ARM64InstrHistogram.cpp 20 | common-internal.h 21 | CompressedFile.cpp 22 | dyld.cpp 23 | exception_handlers.cpp 24 | FridaStalker.cpp 25 | log_structs.cpp 26 | mach.cpp 27 | macho.cpp 28 | MachORegions.cpp 29 | MinimalPerfectHash.cpp 30 | proc.cpp 31 | Signpost.cpp 32 | Symbols.cpp 33 | ThreadPool.cpp 34 | TraceLog.cpp 35 | utils.cpp 36 | VMRegions.cpp 37 | XNUCommpageTime.cpp 38 | XNUTracer.cpp 39 | ) 40 | 41 | set(INSTTRACE_HDR_STANDALONE 42 | Atomic.h 43 | BitVector.h 44 | EliasFano.h 45 | RankSelect.h 46 | drcov.h 47 | xnu-trace.h 48 | xnu-trace-c.h 49 | common.h 50 | ) 51 | 52 | set(INSTTRACE_HDR) 53 | foreach(HDR ${INSTTRACE_HDR_STANDALONE}) 54 | set(HDR "${CMAKE_CURRENT_SOURCE_DIR}/../../include/xnu-trace/${HDR}") 55 | list(APPEND INSTTRACE_HDR ${HDR}) 56 | endforeach() 57 | 58 | foreach(SRC ${INSTTRACE_SRC}) 59 | get_filename_component(HDR_NAME ${SRC} NAME_WLE) 60 | set(HDR "${CMAKE_CURRENT_SOURCE_DIR}/../../include/xnu-trace/${HDR_NAME}.h") 61 | if(EXISTS ${HDR}) 62 | list(APPEND INSTTRACE_HDR ${HDR}) 63 | endif() 64 | endforeach() 65 | 66 | set(XNUTRACE_PUBLIC_LIBS 67 | interval-tree 68 | frida-gum 69 | absl::flat_hash_map 70 | thread-pool 71 | static_vector 72 | icecream 73 | Boost::headers 74 | range-v3 75 | ) 76 | 77 | set(XNUTRACE_PRIVATE_LIBS 78 | bn-arm64-disasm 79 | CoreSymbolication 80 | fmt 81 | libzstd_static 82 | mbedtls 83 | xxhash-xnu-trace 84 | xnu-trace-mach-exc 85 | fastmod 86 | simde 87 | ) 88 | 89 | # INSTTRACE_HDR added for Xcode project generation 90 | add_library(xnu-trace STATIC ${INSTTRACE_SRC} ${INSTTRACE_HDR}) 91 | add_library(xnu-trace-shared SHARED ${INSTTRACE_SRC} ${INSTTRACE_HDR}) 92 | set_target_properties(xnu-trace xnu-trace-shared PROPERTIES PUBLIC_HEADER "${INSTTRACE_HDR}") 93 | 94 | target_link_libraries(xnu-trace 95 | PUBLIC 96 | ${XNUTRACE_PUBLIC_LIBS} 97 | PRIVATE 98 | ${XNUTRACE_PRIVATE_LIBS} 99 | ) 100 | target_compile_options(xnu-trace PRIVATE -Wall -Wextra -Wpedantic) 101 | 102 | target_include_directories(xnu-trace 103 | PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/../../include 104 | PRIVATE ${CMAKE_CURRENT_BINARY_DIR} 105 | ) 106 | 107 | target_link_libraries(xnu-trace-shared 108 | PUBLIC 109 | ${XNUTRACE_PUBLIC_LIBS} 110 | PRIVATE 111 | ${XNUTRACE_PRIVATE_LIBS} 112 | ) 113 | target_compile_options(xnu-trace-shared PRIVATE -Wall -Wextra -Wpedantic) 114 | 115 | target_include_directories(xnu-trace-shared 116 | PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/../../include 117 | PRIVATE ${CMAKE_CURRENT_BINARY_DIR} 118 | ) 119 | 120 | 121 | install(TARGETS xnu-trace xnu-trace-shared 122 | ARCHIVE DESTINATION lib 123 | LIBRARY DESTINATION lib 124 | PUBLIC_HEADER DESTINATION include/xnu-trace 125 | ) 126 | 127 | # target_compile_options(xnu-trace PRIVATE -Xclang -fdump-record-layouts) 128 | -------------------------------------------------------------------------------- /lib/xnu-trace/FridaStalker.cpp: -------------------------------------------------------------------------------- 1 | #include "xnu-trace/FridaStalker.h" 2 | #include "common-internal.h" 3 | 4 | #include "xnu-trace/TraceLog.h" 5 | #include "xnu-trace/xnu-trace-c.h" 6 | 7 | #include 8 | 9 | #include 10 | 11 | #include 12 | 13 | FridaStalker::FridaStalker(const std::string &log_dir_path, bool symbolicate, int compression_level, 14 | bool stream) 15 | : m_log{log_dir_path, compression_level, stream}, m_macho_regions{mach_task_self()}, 16 | m_vm_regions{mach_task_self()} { 17 | gum_init_embedded(); 18 | assert(gum_stalker_is_supported()); 19 | m_stalker = gum_stalker_new(); 20 | assert(m_stalker); 21 | gum_stalker_set_trust_threshold(m_stalker, 0); 22 | cb_ctx.logger = &logger(); 23 | m_transformer = gum_stalker_transformer_make_from_callback( 24 | (GumStalkerTransformerCallback)transform_cb, (void *)&cb_ctx, nullptr); 25 | assert(m_transformer); 26 | if (symbolicate) { 27 | m_symbols = std::make_unique(mach_task_self()); 28 | } 29 | } 30 | 31 | FridaStalker::~FridaStalker() { 32 | write_trace(); 33 | g_object_unref(m_stalker); 34 | g_object_unref(m_transformer); 35 | gum_deinit_embedded(); 36 | fmt::print("{:s}\n", 37 | fmt::format(std::locale("en_US.UTF-8"), "Number of instructions traced: {:Ld}\n", 38 | logger().num_inst())); 39 | } 40 | 41 | void FridaStalker::follow() { 42 | gum_stalker_follow_me(m_stalker, m_transformer, nullptr); 43 | } 44 | 45 | void FridaStalker::follow(size_t thread_id) { 46 | gum_stalker_follow(m_stalker, thread_id, m_transformer, nullptr); 47 | } 48 | 49 | void FridaStalker::unfollow() { 50 | gum_stalker_unfollow_me(m_stalker); 51 | } 52 | 53 | void FridaStalker::unfollow(size_t thread_id) { 54 | gum_stalker_unfollow(m_stalker, thread_id); 55 | } 56 | 57 | void FridaStalker::write_trace() { 58 | logger().write(m_macho_regions, m_symbols.get()); 59 | } 60 | 61 | TraceLog &FridaStalker::logger() { 62 | return m_log; 63 | } 64 | 65 | void FridaStalker::transform_cb(void *iterator, void *output, void *user_data) { 66 | (void)output; 67 | auto *it = (GumStalkerIterator *)iterator; 68 | auto cb_ctx = (CBCtx *)user_data; 69 | cs_insn *insn; 70 | while (gum_stalker_iterator_next(it, (const cs_insn **)&insn)) { 71 | cb_ctx->insn = insn; 72 | gum_stalker_iterator_put_callout(it, (GumStalkerCallout)instruction_cb, user_data, nullptr); 73 | gum_stalker_iterator_keep(it); 74 | } 75 | } 76 | 77 | #if 0 78 | void FridaStalker::instruction_cb(void *context, void *user_data) { 79 | auto ctx = (GumCpuContext *)context; 80 | auto cb_ctx = (CBCtx *)user_data; 81 | cb_ctx->logger->log((uint32_t)gum_process_get_current_thread_id(), ctx->pc); 82 | } 83 | #else 84 | void FridaStalker::instruction_cb(void *context, void *user_data) { 85 | const auto ctx = (log_arm64_cpu_context *)context; 86 | auto cb_ctx = (CBCtx *)user_data; 87 | cb_ctx->logger->log((uint32_t)gum_process_get_current_thread_id(), ctx, cb_ctx->insn); 88 | } 89 | #endif 90 | 91 | // C API 92 | 93 | stalker_t create_stalker(const char *log_dir_path, int symbolicate, int compression_level, 94 | int stream) { 95 | return (stalker_t) new FridaStalker{log_dir_path, !!symbolicate, compression_level, !!stream}; 96 | } 97 | 98 | void destroy_stalker(stalker_t stalker) { 99 | delete (FridaStalker *)stalker; 100 | } 101 | 102 | void stalker_follow_me(stalker_t stalker) { 103 | ((FridaStalker *)stalker)->follow(); 104 | } 105 | 106 | void stalker_follow_thread(stalker_t stalker, size_t thread_id) { 107 | ((FridaStalker *)stalker)->follow(thread_id); 108 | } 109 | 110 | void stalker_unfollow_me(stalker_t stalker) { 111 | ((FridaStalker *)stalker)->unfollow(); 112 | } 113 | 114 | void stalker_unfollow_thread(stalker_t stalker, size_t thread_id) { 115 | ((FridaStalker *)stalker)->unfollow(thread_id); 116 | } 117 | -------------------------------------------------------------------------------- /lib/xnu-trace/MachORegions.cpp: -------------------------------------------------------------------------------- 1 | #include "xnu-trace/MachORegions.h" 2 | #include "common-internal.h" 3 | 4 | #include "xnu-trace/Signpost.h" 5 | #include "xnu-trace/VMRegions.h" 6 | #include "xnu-trace/dyld.h" 7 | #include "xnu-trace/mach.h" 8 | 9 | #include 10 | 11 | fs::path image_info::log_path() const { 12 | const std::span trunc_digest{digest.data(), 4}; 13 | return {fmt::format("macho-region-{:s}-{:02x}.bin", path.filename().string(), 14 | fmt::join(trunc_digest, ""))}; 15 | } 16 | 17 | MachORegions::MachORegions(task_t target_task) : m_target_task{target_task} { 18 | reset(); 19 | } 20 | 21 | MachORegions::MachORegions(const log_region *region_buf, uint64_t num_regions, 22 | std::map> ®ions_bytes) { 23 | for (uint64_t i = 0; i < num_regions; ++i) { 24 | const char *path_ptr = (const char *)(region_buf + 1); 25 | std::string path{path_ptr, region_buf->path_len}; 26 | image_info img_info{.base = region_buf->base, 27 | .size = region_buf->size, 28 | .slide = region_buf->slide, 29 | .path = path, 30 | .is_jit = (bool)region_buf->is_jit}; 31 | memcpy(img_info.uuid, region_buf->uuid, sizeof(img_info.uuid)); 32 | memcpy(img_info.digest.data(), region_buf->digest_sha256, img_info.digest.size()); 33 | img_info.bytes = std::move(regions_bytes[img_info.digest]); 34 | assert(img_info.bytes.size() == img_info.size); 35 | m_regions.emplace_back(img_info); 36 | region_buf = 37 | (log_region *)((uint8_t *)region_buf + sizeof(*region_buf) + region_buf->path_len); 38 | } 39 | std::sort(m_regions.begin(), m_regions.end()); 40 | create_hash(); 41 | } 42 | 43 | void MachORegions::reset() { 44 | assert(m_target_task); 45 | if (m_target_task != mach_task_self()) { 46 | mach_check(task_suspend(m_target_task), "region reset suspend"); 47 | } 48 | m_regions = get_dyld_image_infos(m_target_task); 49 | 50 | const auto cs = CSSymbolicatorCreateWithTask(m_target_task); 51 | assert(!CSIsNull(cs)); 52 | for (auto ®ion : m_regions) { 53 | const auto sym_owner = 54 | CSSymbolicatorGetSymbolOwnerWithAddressAtTime(cs, region.base, kCSNow); 55 | assert(!CSIsNull(sym_owner)); 56 | const auto uuid = CSSymbolOwnerGetCFUUIDBytes(sym_owner); 57 | memcpy(region.uuid, uuid, sizeof(region.uuid)); 58 | } 59 | CSRelease(cs); 60 | 61 | for (auto ®ion : m_regions) { 62 | region.size = roundup_pow2_mul(region.size, PAGE_SZ); 63 | region.bytes = read_target(m_target_task, region.base, region.size); 64 | region.digest = get_sha256(region.bytes); 65 | } 66 | 67 | std::vector region_bases; 68 | for (const auto ®ion : m_regions) { 69 | region_bases.emplace_back(region.base); 70 | } 71 | 72 | int num_jit_regions = 0; 73 | for (const auto &vm_region : get_vm_regions(m_target_task)) { 74 | if (!(vm_region.prot & VM_PROT_EXECUTE)) { 75 | continue; 76 | } 77 | if (!(vm_region.prot & VM_PROT_READ)) { 78 | fmt::print("found XO page at {:#018x}\n", vm_region.base); 79 | } 80 | if (std::find(region_bases.cbegin(), region_bases.cend(), vm_region.base) != 81 | region_bases.cend()) { 82 | continue; 83 | } 84 | if (vm_region.tag != 0xFF) { 85 | continue; 86 | } 87 | const auto bytes = read_target(m_target_task, vm_region.base, vm_region.size); 88 | m_regions.emplace_back(image_info{ 89 | .base = vm_region.base, 90 | .size = vm_region.size, 91 | .path = fmt::format("/tmp/jit-region-{:d}-tag-{:02x}", num_jit_regions, vm_region.tag), 92 | .uuid = {}, 93 | .bytes = std::move(bytes), 94 | .digest = get_sha256(bytes), 95 | .is_jit = true}); 96 | ++num_jit_regions; 97 | } 98 | 99 | std::sort(m_regions.begin(), m_regions.end()); 100 | 101 | create_hash(); 102 | 103 | if (m_target_task != mach_task_self()) { 104 | mach_check(task_resume(m_target_task), "region reset resume"); 105 | } 106 | } 107 | 108 | const image_info &MachORegions::lookup(uint64_t addr) const { 109 | for (const auto &img_info : m_regions) { 110 | if (img_info.base <= addr && addr < img_info.base + img_info.size) { 111 | return img_info; 112 | } 113 | } 114 | assert(!"no region found"); 115 | } 116 | 117 | std::pair MachORegions::lookup_idx(uint64_t addr) const { 118 | size_t idx = 0; 119 | for (const auto &img_info : m_regions) { 120 | if (img_info.base <= addr && addr < img_info.base + img_info.size) { 121 | return std::make_pair(img_info, idx); 122 | } 123 | ++idx; 124 | } 125 | assert(!"no region found"); 126 | } 127 | 128 | const image_info &MachORegions::lookup(const std::string &image_name) const { 129 | std::vector matches; 130 | for (const auto &img_info : m_regions) { 131 | if (img_info.path.filename().string() == image_name) { 132 | matches.emplace_back(&img_info); 133 | } 134 | } 135 | assert(matches.size() == 1); 136 | return *matches[0]; 137 | } 138 | 139 | uint32_t MachORegions::lookup_inst(uint64_t addr) const { 140 | const auto pa = addr >> PAGE_SZ_LOG2; 141 | const auto page_off = addr & (PAGE_SZ - 1); 142 | return *(uint32_t *)(m_pa2buf[pa] + page_off); 143 | } 144 | 145 | const std::vector &MachORegions::regions() const { 146 | return m_regions; 147 | } 148 | 149 | void MachORegions::create_hash() { 150 | std::vector> pa2buf_vec; 151 | // base regions 152 | for (const auto ®ion : m_regions) { 153 | if (region.is_jit) { 154 | continue; 155 | } 156 | for (size_t off = 0; off < region.size; off += PAGE_SZ) { 157 | const auto pa = (region.base + off) >> PAGE_SZ_LOG2; 158 | pa2buf_vec.emplace_back(std::make_pair(pa, region.bytes.data() + off)); 159 | } 160 | } 161 | // jit regions override base regions 162 | for (const auto ®ion : m_regions) { 163 | if (!region.is_jit) { 164 | continue; 165 | } 166 | for (size_t off = 0; off < region.size; off += PAGE_SZ) { 167 | const auto pa = (region.base + off) >> PAGE_SZ_LOG2; 168 | if (auto it = std::find_if(pa2buf_vec.begin(), pa2buf_vec.end(), 169 | [&](const auto p) { 170 | return p.first == pa; 171 | }); 172 | it != pa2buf_vec.end()) { 173 | *it = std::make_pair(pa, region.bytes.data() + off); 174 | } else { 175 | pa2buf_vec.emplace_back(std::make_pair(pa, region.bytes.data() + off)); 176 | } 177 | } 178 | } 179 | 180 | Signpost mph_build_sp("MachORegions", "mph build"); 181 | mph_build_sp.start(); 182 | m_pa2buf.build(pa2buf_vec); 183 | mph_build_sp.end(); 184 | } 185 | 186 | void MachORegions::dump() const { 187 | for (const auto ®ion : m_regions) { 188 | fmt::print("base: {:#018x} => {:#018x} size: {:#010x} slide: {:#x} path: '{:s}'\n", 189 | region.base, region.base + region.size, region.size, region.slide, 190 | region.path.string()); 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /lib/xnu-trace/MinimalPerfectHash.cpp: -------------------------------------------------------------------------------- 1 | #include "xnu-trace/MinimalPerfectHash.h" 2 | #include "common-internal.h" 3 | 4 | #include "xnu-trace/XNUCommpageTime.h" 5 | 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | #define XXH_INLINE_ALL 12 | #define XXH_NAMESPACE xnu_trace_mph_ 13 | #include 14 | 15 | xxhash_64::type xxhash_64::hash(uint64_t val, uint64_t seed) noexcept { 16 | return XXH64(reinterpret_cast(&val), sizeof(val), seed); 17 | } 18 | 19 | xxhash_32::type xxhash_32::hash(uint64_t val, uint64_t seed) noexcept { 20 | return XXH64(reinterpret_cast(&val), sizeof(val), seed); 21 | } 22 | 23 | xxhash3_64::type xxhash3_64::hash(uint64_t val, uint64_t seed) noexcept { 24 | return XXH3_64bits_withSeed(reinterpret_cast(&val), sizeof(val), seed); 25 | } 26 | 27 | xxhash3_32::type xxhash3_32::hash(uint64_t val, uint64_t seed) noexcept { 28 | return XXH3_64bits_withSeed(reinterpret_cast(&val), sizeof(val), seed); 29 | } 30 | 31 | jevhash_64::type jevhash_64::hash(uint64_t val, uint64_t seed) noexcept { 32 | uint64_t acc = seed + 1; 33 | acc *= XXH_PRIME64_2; 34 | acc ^= val; 35 | acc += seed; 36 | acc = XXH_rotl64(acc, 13); 37 | return acc; 38 | } 39 | 40 | jevhash_32::type jevhash_32::hash(uint64_t val, uint64_t seed) noexcept { 41 | uint32_t acc = seed + 1; 42 | acc *= XXH_PRIME32_2; 43 | acc ^= (val >> 32); 44 | acc += seed; 45 | acc ^= (val & 0xFFFF'FFFF); 46 | acc = XXH_rotl32(acc, 13); 47 | return acc; 48 | } 49 | 50 | template struct bucket { 51 | uint32_t hmod; 52 | std::vector keys; 53 | }; 54 | 55 | template 56 | uint32_t MinimalPerfectHash::mod(typename Hasher::type n) const { 57 | if constexpr (std::is_same_v) { 58 | return fastmod::fastmod_u32(n, m_fastmod_u32_M, m_nkeys); 59 | } else { 60 | return n % m_nkeys; 61 | } 62 | } 63 | 64 | template 65 | void MinimalPerfectHash::build(std::span keys) { 66 | assert(keys.size() <= UINT32_MAX); 67 | m_nkeys = (uint32_t)keys.size(); 68 | if constexpr (std::is_same_v) { 69 | m_fastmod_u32_M = fastmod::computeM_u32(m_nkeys); 70 | } 71 | std::vector vkeys{keys.begin(), keys.end()}; 72 | std::sort(vkeys.begin(), vkeys.end()); 73 | vkeys.erase(std::unique(vkeys.begin(), vkeys.end()), vkeys.end()); 74 | assert(vkeys.size() == m_nkeys && "keys for MPH are not unique"); 75 | 76 | std::vector> buckets{m_nkeys}; 77 | for (const auto &key : keys) { 78 | const auto hmod = mod(Hasher::hash(key)); 79 | buckets[hmod].hmod = hmod; 80 | buckets[hmod].keys.emplace_back(key); 81 | } 82 | 83 | std::sort(buckets.begin(), buckets.end(), [](const auto &a, const auto &b) { 84 | return a.keys.size() > b.keys.size(); 85 | }); 86 | 87 | // fmt::print("({:d}, [{:d}])\n", buckets[0].hmod, fmt::join(buckets[0].keys, ", ")); 88 | 89 | m_salts.resize(m_nkeys); 90 | std::vector slot_used(m_nkeys); 91 | 92 | XNUFastTimeout timeout{5'000'000'000, []() { 93 | assert(!"mph construction timed out"); 94 | }}; 95 | for (uint32_t i = 0; i < m_nkeys; ++i) { 96 | const auto hmod = buckets[i].hmod; 97 | const auto bucket_num_keys = buckets[i].keys.size(); 98 | if (bucket_num_keys > 1) { 99 | int32_t d = 1; 100 | while (true) { 101 | #pragma clang diagnostic push 102 | #pragma clang diagnostic ignored "-Wvla-extension" 103 | uint32_t salted_hashes[bucket_num_keys]; 104 | #pragma clang diagnostic pop 105 | const auto salted_hashes_end = salted_hashes + bucket_num_keys; 106 | std::fill(salted_hashes, salted_hashes_end, UINT32_MAX); 107 | bool all_free = true; 108 | for (size_t j = 0; j < bucket_num_keys; ++j) { 109 | const auto shmod = mod(Hasher::hash(buckets[i].keys[j], d)); 110 | if (std::find(salted_hashes, salted_hashes_end, shmod) != salted_hashes_end) { 111 | // collision within salted hashes, try again 112 | all_free = false; 113 | break; 114 | } 115 | salted_hashes[j] = shmod; 116 | all_free &= !slot_used[shmod]; 117 | if (!all_free) { 118 | break; 119 | } 120 | } 121 | if (all_free) { 122 | for (size_t j = 0; j < bucket_num_keys; ++j) { 123 | slot_used[salted_hashes[j]] = true; 124 | } 125 | m_salts[hmod] = d; 126 | break; 127 | } 128 | ++d; 129 | timeout.check(); 130 | } 131 | } else if (bucket_num_keys == 1) { 132 | auto it = std::find(slot_used.begin(), slot_used.end(), false); 133 | assert(it != slot_used.end()); 134 | *it = true; 135 | m_salts[hmod] = -std::distance(slot_used.begin(), it) - 1; 136 | } 137 | } 138 | } 139 | 140 | template 141 | uint32_t MinimalPerfectHash::operator()(KeyT key) const { 142 | const auto hmod = mod(Hasher::hash(key)); 143 | const auto salt_val = m_salts[hmod]; 144 | if (salt_val < 0) { 145 | return -salt_val - 1; 146 | } else { 147 | return mod(Hasher::hash(key, salt_val)); 148 | } 149 | } 150 | 151 | template void MinimalPerfectHash::stats() const { 152 | const auto max_d = ranges::max(m_salts); 153 | fmt::print("max_d: {:d}\n", max_d); 154 | const auto num_empty = ranges::count(m_salts, 0); 155 | fmt::print("empty: {:0.3f}%\n", num_empty * 100.0 / m_nkeys); 156 | } 157 | 158 | template class MinimalPerfectHash; 159 | template class MinimalPerfectHash; 160 | template class MinimalPerfectHash; 161 | template class MinimalPerfectHash; 162 | -------------------------------------------------------------------------------- /lib/xnu-trace/Signpost.cpp: -------------------------------------------------------------------------------- 1 | #include "xnu-trace/Signpost.h" 2 | #include "common-internal.h" 3 | 4 | #include 5 | 6 | static std::map s_log_categories; 7 | 8 | static void post(os_log_t log, os_signpost_type_t type, os_signpost_id_t id, const char *name, 9 | const char *msg) { 10 | uint8_t __attribute__((uninitialized, 11 | aligned(16))) os_fmt_buf[__builtin_os_log_format_buffer_size("%s", msg)]; 12 | _os_signpost_emit_with_name_impl(&__dso_handle, log, type, id, name, "%s", 13 | (uint8_t *)__builtin_os_log_format(os_fmt_buf, "%s", msg), 14 | sizeof(os_fmt_buf)); 15 | } 16 | 17 | Signpost::Signpost(const std::string &category, const std::string &name, bool event, 18 | std::string msg) 19 | : m_name{name}, m_event{event} { 20 | if (!s_log_categories.contains(category)) { 21 | s_log_categories[category] = os_log_create(SUBSYSTEM, category.c_str()); 22 | } 23 | m_log = s_log_categories[category]; 24 | if (!os_signpost_enabled(m_log)) { 25 | m_disabled = true; 26 | return; 27 | } 28 | if (m_event) { 29 | post(m_log, OS_SIGNPOST_EVENT, OS_SIGNPOST_ID_EXCLUSIVE, m_name.c_str(), msg.c_str()); 30 | return; 31 | } 32 | m_id = os_signpost_id_make_with_pointer(m_log, this); 33 | if (m_id == OS_SIGNPOST_ID_NULL || m_id == OS_SIGNPOST_ID_INVALID) { 34 | m_disabled = true; 35 | return; 36 | } 37 | } 38 | 39 | void Signpost::start(std::string msg) { 40 | assert(!m_event); 41 | if (m_disabled) { 42 | return; 43 | } 44 | post(m_log, OS_SIGNPOST_INTERVAL_BEGIN, m_id, m_name.c_str(), msg.c_str()); 45 | m_start_atus = xnu_commpage_time_atus(); 46 | } 47 | 48 | void Signpost::end(std::string msg) { 49 | assert(!m_event); 50 | if (m_disabled) { 51 | return; 52 | } 53 | post(m_log, OS_SIGNPOST_INTERVAL_END, m_id, m_name.c_str(), msg.c_str()); 54 | } 55 | -------------------------------------------------------------------------------- /lib/xnu-trace/Symbols.cpp: -------------------------------------------------------------------------------- 1 | #include "xnu-trace/Symbols.h" 2 | #include "common-internal.h" 3 | 4 | #include "xnu-trace/utils.h" 5 | 6 | #include 7 | 8 | using namespace lib_interval_tree; 9 | 10 | std::vector get_symbols(task_t target_task) { 11 | const auto cs = CSSymbolicatorCreateWithTask(target_task); 12 | assert(!CSIsNull(cs)); 13 | __block std::vector res; 14 | CSSymbolicatorForeachSymbolAtTime(cs, kCSNow, ^(CSSymbolRef sym) { 15 | const auto rng = CSSymbolGetRange(sym); 16 | const auto *name_cstr = CSSymbolGetMangledName(sym); 17 | const std::string name = name_cstr ? name_cstr : "n/a"; 18 | const auto sym_owner = CSSymbolGetSymbolOwner(sym); 19 | assert(!CSIsNull(sym_owner)); 20 | const auto *sym_owner_path_cstr = CSSymbolOwnerGetPath(sym_owner); 21 | const std::string sym_owner_path = sym_owner_path_cstr ? sym_owner_path_cstr : "n/a"; 22 | const auto *sym_owner_name_cstr = CSSymbolOwnerGetName(sym_owner); 23 | const std::string sym_owner_name = sym_owner_name_cstr ? sym_owner_name_cstr : "n/a"; 24 | res.emplace_back(sym_info{ 25 | .base = rng.location, .size = rng.length, .name = name, .path = sym_owner_path}); 26 | return 0; 27 | }); 28 | CSRelease(cs); 29 | 30 | std::sort(res.begin(), res.end()); 31 | 32 | return res; 33 | } 34 | 35 | std::vector get_symbols_in_intervals(const std::vector &syms, 36 | const interval_tree_t &intervals) { 37 | std::vector res; 38 | const auto missing = intervals.cend(); 39 | for (const auto &sym : syms) { 40 | if (intervals.overlap_find({sym.base, sym.base + sym.size}) != missing) { 41 | res.emplace_back(sym); 42 | } 43 | } 44 | std::sort(res.begin(), res.end()); 45 | return res; 46 | } 47 | 48 | Symbols::Symbols(task_t target_task) : m_target_task{target_task} { 49 | reset(); 50 | } 51 | 52 | Symbols::Symbols(const log_sym *sym_buf, uint64_t num_syms) { 53 | for (uint64_t i = 0; i < num_syms; ++i) { 54 | const auto *name_ptr = (char *)(sym_buf + 1); 55 | const std::string name{name_ptr, sym_buf->name_len}; 56 | const auto *path_ptr = (char *)(sym_buf + 1) + sym_buf->name_len; 57 | const std::string path{path_ptr, sym_buf->path_len}; 58 | sym_info sym{.base = sym_buf->base, .size = sym_buf->size, .name = name, .path = path}; 59 | m_syms.emplace_back(sym); 60 | sym_buf = (log_sym *)((uint8_t *)sym_buf + sizeof(*sym_buf) + sym_buf->name_len + 61 | sym_buf->path_len); 62 | } 63 | std::sort(m_syms.begin(), m_syms.end()); 64 | } 65 | 66 | void Symbols::reset() { 67 | assert(m_target_task); 68 | if (m_target_task != mach_task_self()) { 69 | mach_check(task_suspend(m_target_task), "symbols reset suspend"); 70 | } 71 | m_syms = get_symbols(m_target_task); 72 | std::sort(m_syms.begin(), m_syms.end()); 73 | if (m_target_task != mach_task_self()) { 74 | mach_check(task_resume(m_target_task), "symbols reset resume"); 75 | } 76 | } 77 | 78 | const sym_info *Symbols::lookup(uint64_t addr) const { 79 | for (const auto &sym : m_syms) { 80 | if (sym.base <= addr && addr < sym.base + sym.size) { 81 | return &sym; 82 | } 83 | } 84 | return nullptr; 85 | } 86 | 87 | const std::vector &Symbols::syms() const { 88 | return m_syms; 89 | } 90 | -------------------------------------------------------------------------------- /lib/xnu-trace/ThreadPool.cpp: -------------------------------------------------------------------------------- 1 | #include "xnu-trace/ThreadPool.h" 2 | #include "common-internal.h" 3 | 4 | XNUTraceThreadPool xnutrace_pool; 5 | -------------------------------------------------------------------------------- /lib/xnu-trace/VMRegions.cpp: -------------------------------------------------------------------------------- 1 | #include "xnu-trace/VMRegions.h" 2 | #include "common-internal.h" 3 | 4 | #include "xnu-trace/utils.h" 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | std::vector get_vm_regions(task_t target_task) { 15 | std::vector res; 16 | vm_address_t addr = 0; 17 | kern_return_t kr; 18 | natural_t depth = 0; 19 | while (true) { 20 | vm_size_t sz{}; 21 | vm_region_submap_info_64 info{}; 22 | mach_msg_type_number_t cnt = VM_REGION_SUBMAP_INFO_COUNT_64; 23 | kr = vm_region_recurse_64(target_task, &addr, &sz, &depth, (vm_region_recurse_info_t)&info, 24 | &cnt); 25 | if (kr != KERN_SUCCESS) { 26 | if (kr != KERN_INVALID_ADDRESS) { 27 | fmt::print("Error: '{:s}' retval: {:d} description: '{:s}'\n", "get_vm_regions", kr, 28 | mach_error_string(kr)); 29 | } 30 | break; 31 | } 32 | #if 0 33 | if (info.protection & 1 && sz && !info.is_submap) { 34 | const auto buf = read_target(target_task, addr, 128); 35 | hexdump(buf.data(), buf.size()); 36 | } 37 | #endif 38 | res.emplace_back(region{.base = addr, 39 | .size = sz, 40 | .depth = depth, 41 | .prot = info.protection, 42 | .tag = info.user_tag, 43 | .submap = !!info.is_submap}); 44 | if (info.is_submap) { 45 | depth += 1; 46 | continue; 47 | } 48 | addr += sz; 49 | } 50 | 51 | std::sort(res.begin(), res.end()); 52 | 53 | #if 0 54 | for (const auto &map : res) { 55 | std::string l; 56 | std::fill_n(std::back_inserter(l), map.depth, '\t'); 57 | l += fmt::format("{:#018x}-{:#018x} {:s} {:#x} {:#04x}", map.base, map.base + map.size, 58 | prot_to_str(map.prot), map.size, map.tag); 59 | fmt::print("{:s}\n", l); 60 | } 61 | #endif 62 | 63 | return res; 64 | } 65 | 66 | VMRegions::VMRegions(task_t target_task) : m_target_task{target_task} { 67 | reset(); 68 | } 69 | 70 | void VMRegions::reset() { 71 | if (m_target_task != mach_task_self()) { 72 | mach_check(task_suspend(m_target_task), "region reset suspend"); 73 | } 74 | m_all_regions = get_vm_regions(m_target_task); 75 | if (m_target_task != mach_task_self()) { 76 | mach_check(task_resume(m_target_task), "region reset resume"); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /lib/xnu-trace/XNUCommpageTime.cpp: -------------------------------------------------------------------------------- 1 | #include "xnu-trace/XNUCommpageTime.h" 2 | #include "common-internal.h" 3 | 4 | #include 5 | 6 | #if !defined(__APPLE__) && !defined(__arm64__) 7 | #error bad platform 8 | #endif 9 | 10 | struct commpage_timeofday_data { 11 | uint64_t TimeStamp_tick; 12 | uint64_t TimeStamp_sec; 13 | uint64_t TimeStamp_frac; 14 | uint64_t Ticks_scale; 15 | uint64_t Ticks_per_sec; 16 | }; 17 | 18 | static constexpr uint64_t _COMM_PAGE_START_ADDRESS = 0x0000'000F'FFFF'C000ull; 19 | static constexpr uint64_t _COMM_PAGE_APPROX_TIME = _COMM_PAGE_START_ADDRESS + 0x0C0; 20 | static constexpr uint64_t _COMM_PAGE_NEWTIMEOFDAY_DATA = _COMM_PAGE_START_ADDRESS + 0x120; 21 | static constexpr uint64_t _COMM_PAGE_NEWTIMEOFDAY_SECONDS = 22 | _COMM_PAGE_NEWTIMEOFDAY_DATA + offsetof(commpage_timeofday_data, TimeStamp_sec); 23 | 24 | uint64_t xnu_commpage_time_seconds() { 25 | return *(volatile const uint64_t *)_COMM_PAGE_NEWTIMEOFDAY_SECONDS; 26 | } 27 | 28 | uint64_t xnu_commpage_time_atus() { 29 | return *(volatile const uint64_t *)_COMM_PAGE_APPROX_TIME; 30 | } 31 | 32 | uint64_t xnu_commpage_time_atus_to_ns(uint64_t atus) { 33 | static bool tb_inited = false; 34 | static mach_timebase_info_data_t tb_info; 35 | if (!tb_inited) { 36 | mach_check(mach_timebase_info(&tb_info), "xnu_commpage_time_atus_to_ns mach_timebase_info"); 37 | tb_inited = true; 38 | } 39 | return (double)atus * tb_info.numer / tb_info.denom; 40 | } 41 | -------------------------------------------------------------------------------- /lib/xnu-trace/common-internal.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #undef NDEBUG 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | namespace fs = std::filesystem; 11 | using namespace std::string_literals; 12 | -------------------------------------------------------------------------------- /lib/xnu-trace/dyld.cpp: -------------------------------------------------------------------------------- 1 | #include "xnu-trace/dyld.h" 2 | #include "common-internal.h" 3 | 4 | #include "xnu-trace/mach.h" 5 | #include "xnu-trace/macho.h" 6 | 7 | #include 8 | #include 9 | 10 | std::vector get_dyld_image_infos(task_t target_task) { 11 | std::vector res; 12 | task_dyld_info_data_t dyld_info; 13 | mach_msg_type_number_t cnt = TASK_DYLD_INFO_COUNT; 14 | mach_check(task_info(target_task, TASK_DYLD_INFO, (task_info_t)&dyld_info, &cnt), 15 | "task_info dyld info"); 16 | assert(dyld_info.all_image_info_format == TASK_DYLD_ALL_IMAGE_INFO_64); 17 | const auto all_info_buf = 18 | read_target(target_task, dyld_info.all_image_info_addr, dyld_info.all_image_info_size); 19 | const dyld_all_image_infos *all_img_infos = (dyld_all_image_infos *)all_info_buf.data(); 20 | 21 | if (!all_img_infos->infoArray) { 22 | return res; 23 | } 24 | 25 | const auto dyld_base = (uint64_t)all_img_infos->dyldImageLoadAddress; 26 | const auto dyld_macho_segs = read_macho_segs_target(target_task, dyld_base); 27 | 28 | res.emplace_back(image_info{.base = dyld_base, 29 | .size = get_text_size(dyld_macho_segs), 30 | .slide = dyld_base - get_text_base(dyld_macho_segs), 31 | .path = read_cstr_target(target_task, all_img_infos->dyldPath), 32 | .uuid = {}, 33 | .is_jit = false}); 34 | 35 | const auto infos_buf = read_target(target_task, all_img_infos->infoArray, 36 | all_img_infos->infoArrayCount * sizeof(dyld_image_info)); 37 | const auto img_infos = std::span{(dyld_image_info *)infos_buf.data(), 38 | all_img_infos->infoArrayCount}; 39 | for (const auto &img_info : img_infos) { 40 | const auto img_base = (uint64_t)img_info.imageLoadAddress; 41 | const auto macho_segs = read_macho_segs_target(target_task, img_base); 42 | res.emplace_back(image_info{.base = img_base, 43 | .size = get_text_size(macho_segs), 44 | .slide = img_base - get_text_base(macho_segs), 45 | .path = read_cstr_target(target_task, img_info.imageFilePath), 46 | .uuid = {}, 47 | .is_jit = false}); 48 | } 49 | 50 | std::sort(res.begin(), res.end()); 51 | 52 | return res; 53 | } 54 | -------------------------------------------------------------------------------- /lib/xnu-trace/exception_handlers.cpp: -------------------------------------------------------------------------------- 1 | #include "common-internal.h" 2 | 3 | #include "xnu-trace/XNUTracer.h" 4 | #include "xnu-trace/mach.h" 5 | 6 | #include 7 | 8 | // Handle EXCEPTION_STATE_IDENTIY behavior 9 | extern "C" kern_return_t trace_catch_mach_exception_raise_state_identity( 10 | mach_port_t exception_port, mach_port_t thread, mach_port_t task, exception_type_t exception, 11 | mach_exception_data_t code, mach_msg_type_number_t code_count, int *flavor, 12 | thread_state_t old_state, mach_msg_type_number_t old_state_count, thread_state_t new_state, 13 | mach_msg_type_number_t *new_state_count) { 14 | #pragma unused(exception_port, task, exception, code, code_count, flavor) 15 | 16 | auto os = (const arm_thread_state64_t *)old_state; 17 | auto ns = (arm_thread_state64_t *)new_state; 18 | 19 | const auto opc = arm_thread_state64_get_pc(*os); 20 | // fmt::print(stderr, "exc pc: {:p} {:p}\n", (void *)opc); 21 | 22 | *new_state_count = old_state_count; 23 | *ns = *os; 24 | 25 | g_tracer->logger().log(thread, opc); 26 | 27 | set_single_step_thread(thread, true); 28 | 29 | return KERN_SUCCESS; 30 | } 31 | 32 | // Handle EXCEPTION_DEFAULT behavior 33 | extern "C" kern_return_t trace_catch_mach_exception_raise(mach_port_t exception_port, 34 | mach_port_t thread, mach_port_t task, 35 | exception_type_t exception, 36 | mach_exception_data_t code, 37 | mach_msg_type_number_t code_count) { 38 | #pragma unused(exception_port, thread, task, exception, code, code_count) 39 | assert(!"catch_mach_exception_raise not to be called"); 40 | return KERN_NOT_SUPPORTED; 41 | } 42 | 43 | // Handle EXCEPTION_STATE behavior 44 | extern "C" kern_return_t trace_catch_mach_exception_raise_state( 45 | mach_port_t exception_port, exception_type_t exception, const mach_exception_data_t code, 46 | mach_msg_type_number_t code_count, int *flavor, const thread_state_t old_state, 47 | mach_msg_type_number_t old_state_count, thread_state_t new_state, 48 | mach_msg_type_number_t *new_state_count) { 49 | #pragma unused(exception_port, exception, code, code_count, flavor, old_state, old_state_count, \ 50 | new_state, new_state_count) 51 | assert(!"catch_mach_exception_raise_state not to be called"); 52 | return KERN_NOT_SUPPORTED; 53 | } 54 | -------------------------------------------------------------------------------- /lib/xnu-trace/log_structs.cpp: -------------------------------------------------------------------------------- 1 | #include "xnu-trace/log_structs.h" 2 | #include "common-internal.h" 3 | 4 | void log_arm64_cpu_context::update(const log_msg &msg) { 5 | if (auto sync_ctx = msg.sync_ctx()) { 6 | memcpy(this, sync_ctx, sizeof(*this)); 7 | return; 8 | } 9 | if (msg.pc_branched()) { 10 | pc = msg.pc(); 11 | } 12 | if (msg.sp_changed()) { 13 | sp = msg.sp(); 14 | } 15 | auto gpr_ptr = &x[0]; 16 | for (uint32_t i = 0; i < msg.num_gpr(); ++i) { 17 | gpr_ptr[msg.gpr_idx(i)] = msg.gpr(i); 18 | } 19 | for (uint32_t i = 0; i < msg.num_vec(); ++i) { 20 | v[msg.vec_idx(i)] = msg.vec(i); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/xnu-trace/mach.cpp: -------------------------------------------------------------------------------- 1 | #include "xnu-trace/mach.h" 2 | #include "common-internal.h" 3 | 4 | #include "xnu-trace/utils.h" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | 14 | static_assert(sizeof(GumCpuContext) == sizeof(log_arm64_cpu_context), 15 | "thread state mismatch with frida"); 16 | 17 | bool task_is_valid(task_t task) { 18 | if (!MACH_PORT_VALID(task)) { 19 | return false; 20 | } 21 | pid_t pid; 22 | return pid_for_task(task, &pid) == KERN_SUCCESS; 23 | } 24 | 25 | std::vector read_target(task_t target_task, uint64_t target_addr, uint64_t sz) { 26 | std::vector res; 27 | res.resize(sz); 28 | vm_size_t vm_sz = sz; 29 | const auto kr = vm_read_overwrite(target_task, (vm_address_t)target_addr, sz, 30 | (vm_address_t)res.data(), &vm_sz); 31 | mach_check(kr, "vm_read_overwrite"); 32 | assert(vm_sz == sz); 33 | return res; 34 | } 35 | 36 | std::string read_cstr_target(task_t target_task, uint64_t target_addr) { 37 | std::vector buf; 38 | do { 39 | const auto end_addr = 40 | target_addr % PAGE_SZ ? roundup_pow2_mul(target_addr, PAGE_SZ) : target_addr + PAGE_SZ; 41 | const auto smol_buf = read_target(target_task, target_addr, end_addr - target_addr); 42 | buf.insert(buf.end(), smol_buf.cbegin(), smol_buf.cend()); 43 | target_addr = end_addr; 44 | } while (std::find(buf.cbegin(), buf.cend(), '\0') == buf.cend()); 45 | return {(char *)buf.data()}; 46 | } 47 | 48 | std::string read_cstr_target(task_t target_task, const char *target_addr) { 49 | return read_cstr_target(target_task, (uint64_t)target_addr); 50 | } 51 | 52 | integer_t get_suspend_count(task_t task) { 53 | task_basic_info_64_data_t info; 54 | mach_msg_type_number_t cnt = TASK_BASIC_INFO_64_COUNT; 55 | const auto kr = task_info(task, TASK_BASIC_INFO_64, (task_info_t)&info, &cnt); 56 | mach_check(kr, "get_suspend_count task_info"); 57 | return info.suspend_count; 58 | } 59 | 60 | pid_t pid_for_task(task_t task) { 61 | assert(task); 62 | int pid; 63 | mach_check(pid_for_task(task, &pid), "pid_for_task"); 64 | return (pid_t)pid; 65 | } 66 | 67 | int64_t get_task_for_pid_count(task_t task) { 68 | struct task_extmod_info info; 69 | mach_msg_type_number_t count = TASK_EXTMOD_INFO_COUNT; 70 | const auto kr = task_info(task, TASK_EXTMOD_INFO, (task_info_t)&info, &count); 71 | mach_check(kr, "get_task_for_pid_count thread_info"); 72 | return info.extmod_statistics.task_for_pid_count; 73 | } 74 | 75 | void set_single_step_thread(thread_t thread, bool do_ss) { 76 | // fmt::print("thread {} ss: {}\n", thread, do_ss); 77 | 78 | mach_msg_type_number_t dbg_cnt = ARM_DEBUG_STATE64_COUNT; 79 | 80 | #ifdef READ_DEBUG_STATE 81 | arm_debug_state64_t dbg_state; 82 | const auto kr_thread_get = 83 | thread_get_state(thread, ARM_DEBUG_STATE64, (thread_state_t)&dbg_state, &dbg_cnt); 84 | assert(kr_thread_get == KERN_SUCCESS); 85 | // mach_check(kr_thread_get, 86 | // fmt::format("single_step({:s}) thread_get_state", do_ss ? "true" : "false")); 87 | #else 88 | arm_debug_state64_t dbg_state{}; 89 | #endif 90 | 91 | dbg_state.__mdscr_el1 = (dbg_state.__mdscr_el1 & ~1) | do_ss; 92 | 93 | const auto kr_thread_set = 94 | thread_set_state(thread, ARM_DEBUG_STATE64, (thread_state_t)&dbg_state, dbg_cnt); 95 | assert(kr_thread_set == KERN_SUCCESS); 96 | // mach_check(kr_thread_set, 97 | // fmt::format("single_step({:s}) thread_set_state", do_ss ? "true" : "false")); 98 | } 99 | 100 | void set_single_step_task(task_t task, bool do_ss) { 101 | // fmt::print("task {} ss: {}\n", task, do_ss); 102 | 103 | arm_debug_state64_t dbg_state; 104 | mach_msg_type_number_t dbg_cnt = ARM_DEBUG_STATE64_COUNT; 105 | const auto kr_task_get = 106 | task_get_state(task, ARM_DEBUG_STATE64, (thread_state_t)&dbg_state, &dbg_cnt); 107 | mach_check(kr_task_get, "set_single_step_task task_get_state"); 108 | 109 | dbg_state.__mdscr_el1 = (dbg_state.__mdscr_el1 & ~1) | do_ss; 110 | 111 | const auto kr_task_set = 112 | task_set_state(task, ARM_DEBUG_STATE64, (thread_state_t)&dbg_state, dbg_cnt); 113 | mach_check(kr_task_set, "set_single_step_task task_set_state"); 114 | 115 | thread_act_array_t thread_list; 116 | mach_msg_type_number_t num_threads; 117 | const auto kr_threads = task_threads(task, &thread_list, &num_threads); 118 | mach_check(kr_threads, "set_single_step_task task_threads"); 119 | for (mach_msg_type_number_t i = 0; i < num_threads; ++i) { 120 | set_single_step_thread(thread_list[i], do_ss); 121 | } 122 | const auto kr_dealloc = vm_deallocate(mach_task_self(), (vm_address_t)thread_list, 123 | sizeof(thread_act_t) * num_threads); 124 | mach_check(kr_dealloc, "set_single_step_task vm_deallocate"); 125 | } 126 | 127 | void read_target_thread_cpu_context(thread_t thread, log_arm64_cpu_context *ctx) { 128 | const auto is_self = thread == mach_thread_self(); 129 | assert(!is_self); 130 | assert(thread_suspend(thread) == KERN_SUCCESS); 131 | 132 | mach_msg_type_number_t gpr_cnt = ARM_THREAD_STATE64_COUNT; 133 | arm_thread_state64_t gpr_state; 134 | const auto kr_thread_get_gpr = 135 | thread_get_state(thread, ARM_THREAD_STATE64, (thread_state_t)&gpr_state, &gpr_cnt); 136 | assert(kr_thread_get_gpr == KERN_SUCCESS); 137 | 138 | mach_msg_type_number_t vec_cnt = ARM_NEON_STATE64_COUNT; 139 | arm_neon_state64_t vec_state; 140 | const auto kr_thread_get_vec = 141 | thread_get_state(thread, ARM_NEON_STATE64, (thread_state_t)&vec_state, &vec_cnt); 142 | assert(kr_thread_get_vec == KERN_SUCCESS); 143 | 144 | ctx->pc = arm_thread_state64_get_pc(gpr_state); 145 | ctx->sp = arm_thread_state64_get_sp(gpr_state); 146 | ctx->nzcv = 0; 147 | memcpy(ctx->x, gpr_state.__x, sizeof(ctx->x)); 148 | ctx->fp = arm_thread_state64_get_fp(gpr_state); 149 | ctx->lr = arm_thread_state64_get_lr(gpr_state); 150 | memcpy(ctx->v, vec_state.__v, sizeof(ctx->v)); 151 | 152 | assert(thread_resume(thread) == KERN_SUCCESS); 153 | } 154 | -------------------------------------------------------------------------------- /lib/xnu-trace/mach_exc.defs: -------------------------------------------------------------------------------- 1 | #define catch_ trace_catch_ 2 | ServerDemux mach_exc_trace_server; 3 | #include 4 | -------------------------------------------------------------------------------- /lib/xnu-trace/macho.cpp: -------------------------------------------------------------------------------- 1 | #include "xnu-trace/macho.h" 2 | #include "common-internal.h" 3 | 4 | #include "xnu-trace/mach.h" 5 | 6 | std::vector read_macho_segs_target(task_t target_task, 7 | uint64_t macho_hdr_addr) { 8 | std::vector segs; 9 | const auto hdr_buf = read_target(target_task, macho_hdr_addr, sizeof(mach_header_64)); 10 | const auto hdr = (mach_header_64 *)hdr_buf.data(); 11 | const auto cmd_buf = 12 | read_target(target_task, macho_hdr_addr + sizeof(mach_header_64), hdr->sizeofcmds); 13 | const auto end_of_lc = (load_command *)(cmd_buf.data() + hdr->sizeofcmds); 14 | for (auto lc = (load_command *)cmd_buf.data(); lc < end_of_lc; 15 | lc = (load_command *)((uint8_t *)lc + lc->cmdsize)) { 16 | if (lc->cmd != LC_SEGMENT_64) { 17 | continue; 18 | } 19 | const auto seg = (segment_command_64 *)lc; 20 | if (!strncmp(seg->segname, "__PAGEZERO", sizeof(seg->segname))) { 21 | continue; 22 | } 23 | segs.emplace_back(*seg); 24 | } 25 | return segs; 26 | } 27 | 28 | std::vector read_macho_segs_target(task_t target_task, 29 | const mach_header_64 *macho_hdr) { 30 | return read_macho_segs_target(target_task, (uint64_t)macho_hdr); 31 | } 32 | 33 | uint64_t get_text_size(const std::vector &segments) { 34 | uint32_t num_exec_segs = 0; 35 | uint64_t exec_file_sz; 36 | for (const auto &seg : segments) { 37 | if (seg.maxprot & VM_PROT_EXECUTE) { 38 | ++num_exec_segs; 39 | exec_file_sz = seg.vmsize; 40 | } 41 | } 42 | assert(num_exec_segs == 1); 43 | return exec_file_sz; 44 | } 45 | 46 | uint64_t get_text_base(const std::vector &segments) { 47 | uint32_t num_exec_segs = 0; 48 | uint64_t base; 49 | for (const auto &seg : segments) { 50 | if (seg.maxprot & VM_PROT_EXECUTE) { 51 | ++num_exec_segs; 52 | base = seg.vmaddr; 53 | } 54 | } 55 | assert(num_exec_segs == 1); 56 | return base; 57 | } 58 | -------------------------------------------------------------------------------- /lib/xnu-trace/proc.cpp: -------------------------------------------------------------------------------- 1 | #include "xnu-trace/proc.h" 2 | #include "common-internal.h" 3 | 4 | #include "xnu-trace/utils.h" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | int32_t get_context_switch_count(pid_t pid) { 13 | proc_taskinfo ti; 14 | const auto res = proc_pidinfo(pid, PROC_PIDTASKINFO, 0, &ti, sizeof(ti)); 15 | if (res != sizeof(ti)) { 16 | fmt::print(stderr, "get_context_switch_count proc_pidinfo returned {:d}", res); 17 | } 18 | return ti.pti_csw; 19 | } 20 | 21 | pid_t pid_for_name(std::string process_name) { 22 | const auto proc_num = proc_listpids(PROC_ALL_PIDS, 0, nullptr, 0) / sizeof(pid_t); 23 | std::vector pids; 24 | pids.resize(proc_num); 25 | const auto actual_proc_num = 26 | proc_listpids(PROC_ALL_PIDS, 0, pids.data(), (int)bytesizeof(pids)) / sizeof(pid_t); 27 | assert(actual_proc_num > 0); 28 | pids.resize(actual_proc_num); 29 | std::vector> matches; 30 | for (const auto pid : pids) { 31 | if (!pid) { 32 | continue; 33 | } 34 | char path_buf[PROC_PIDPATHINFO_MAXSIZE]; 35 | if (proc_pidpath(pid, path_buf, sizeof(path_buf)) > 0) { 36 | std::filesystem::path path{path_buf}; 37 | if (path.filename().string() == process_name) { 38 | matches.emplace_back(path, pid); 39 | } 40 | } 41 | } 42 | if (!matches.size()) { 43 | fmt::print(stderr, "Couldn't find process named '{:s}'\n", process_name); 44 | exit(-1); 45 | } else if (matches.size() > 1) { 46 | fmt::print(stderr, "Found multiple processes named '{:s}'\n", process_name); 47 | exit(-1); 48 | } 49 | return matches[0].second; 50 | } 51 | -------------------------------------------------------------------------------- /python/setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | name = xnutrace 3 | author = Jevin Sweval 4 | author_email = jevinsweval@gmail.com 5 | description = "Tracing of iOS/macOS binaries using HW single step and Frida DBI." 6 | version = attr: xnutrace.__version__ 7 | long_description = file: README.md 8 | long_description_content_type = text/markdown 9 | url = https://github.com/jevinskie/xnu-trace 10 | 11 | [options] 12 | python_requires = ~=3.10 13 | packages = find: 14 | zip_safe = True 15 | install_requires = 16 | attrs 17 | colorcet 18 | datashader 19 | numba 20 | numpy 21 | xarray 22 | zstandard 23 | 24 | [options.entry_points] 25 | console_scripts = 26 | xnu-trace-find-loops-py = xnutrace.tools.find_loops:main 27 | xnu-trace-render-trace-py = xnutrace.tools.render_trace:main 28 | xnu-trace-transmorgifier-py = xnutrace.tools.transmorgifier:main 29 | 30 | [build-system] 31 | requires = ["setuptools"] 32 | -------------------------------------------------------------------------------- /python/setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from setuptools import setup 4 | 5 | setup() 6 | -------------------------------------------------------------------------------- /python/xnutrace/__init__.py: -------------------------------------------------------------------------------- 1 | from xnutrace import compressedfile, tracelog 2 | 3 | __version__ = "0.1.0" 4 | -------------------------------------------------------------------------------- /python/xnutrace/compressedfile.py: -------------------------------------------------------------------------------- 1 | import locale 2 | import mmap 3 | import struct 4 | 5 | import zstandard 6 | 7 | locale.setlocale(locale.LC_ALL, "") 8 | 9 | # struct log_comp_hdr { 10 | # uint64_t magic; 11 | # uint64_t is_compressed; 12 | # uint64_t header_size; 13 | # uint64_t decompressed_size; 14 | # } 15 | log_comp_hdr_t = struct.Struct("=QQQQ") 16 | 17 | 18 | class CompressedFile: 19 | def __init__(self, path: str, magic: int, hdr_size: int) -> None: 20 | self.path = path 21 | self.fh = open(path, "rb") 22 | comp_hdr_buf = self.fh.read(log_comp_hdr_t.size) 23 | (self.magic, is_comp, self.header_size, self.decompressed_size) = log_comp_hdr_t.unpack( 24 | comp_hdr_buf 25 | ) 26 | self.is_compressed = bool(is_comp) 27 | assert self.magic == magic 28 | assert self.header_size == hdr_size 29 | self.header_buf = self.fh.read(self.header_size) 30 | self.data_off = log_comp_hdr_t.size + self.header_size 31 | if self.is_compressed: 32 | self.decomp_mmap = mmap.mmap(-1, self.decompressed_size) 33 | decompressor = zstandard.ZstdDecompressor() 34 | self.fh.seek(self.data_off) 35 | (num_bytes_read, num_bytes_written) = decompressor.copy_stream( 36 | self.fh, self.decomp_mmap 37 | ) 38 | print(f"'{path}' decompressed {num_bytes_read:n} bytes to {num_bytes_written:n} bytes") 39 | else: 40 | self.decomp_mmap = self.fh.read(self.decompressed_size) 41 | 42 | def __bytes__(self) -> mmap.mmap: 43 | return bytes(self.decomp_mmap) 44 | 45 | def header(self) -> bytes: 46 | return self.header_buf 47 | -------------------------------------------------------------------------------- /python/xnutrace/tools/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jevinskie/xnu-trace/f6f0d03440fc381246a13ccaae75f67d6ffa0fac/python/xnutrace/tools/__init__.py -------------------------------------------------------------------------------- /python/xnutrace/tools/find_loops.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import argparse 4 | 5 | from xnutrace.tracelog import TraceLog 6 | 7 | 8 | def real_main(args): 9 | tl = TraceLog(args.trace_dir) 10 | if args.dump: 11 | tl.dump() 12 | 13 | 14 | def get_arg_parser() -> argparse.ArgumentParser: 15 | parser = argparse.ArgumentParser(description="find-loops") 16 | parser.add_argument("-t", "--trace-dir", required=True, help="input trace directory") 17 | parser.add_argument("-d", "--dump", action="store_true", help="dump trace file") 18 | parser.add_argument("-n", "--image-name", required=True, help="image name to find loops in") 19 | return parser 20 | 21 | 22 | def main(): 23 | real_main(get_arg_parser().parse_args()) 24 | 25 | 26 | if __name__ == "__main__": 27 | main() 28 | -------------------------------------------------------------------------------- /python/xnutrace/tools/render_trace.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import argparse 4 | 5 | import colorcet 6 | import datashader as ds 7 | import numpy as np 8 | import pandas as pd 9 | from xnutrace.tracelog import TraceLog 10 | 11 | 12 | def real_main(args): 13 | tl = TraceLog(args.trace_dir) 14 | pcs = tl.based_pcs_for_image(args.image_name) 15 | idx = np.arange(len(pcs)) 16 | raw_points = np.dstack((pcs, idx))[0, :, :] 17 | df = pd.DataFrame(raw_points, columns=("addr", "time"), copy=False) 18 | canvas = ds.Canvas(plot_width=850, plot_height=500) 19 | agg = canvas.points(df, "time", "addr") 20 | img = ds.tf.shade(agg, cmap=colorcet.fire) 21 | img.to_pil().save(args.output) 22 | 23 | 24 | def get_arg_parser() -> argparse.ArgumentParser: 25 | parser = argparse.ArgumentParser(description="find-loops") 26 | parser.add_argument("-t", "--trace-dir", required=True, help="input trace directory") 27 | parser.add_argument("-o", "--output", required=True, help="render png file") 28 | parser.add_argument("-n", "--image-name", required=True, help="image name to find loops in") 29 | return parser 30 | 31 | 32 | def main(): 33 | real_main(get_arg_parser().parse_args()) 34 | 35 | 36 | if __name__ == "__main__": 37 | main() 38 | -------------------------------------------------------------------------------- /python/xnutrace/tools/transmorgifier.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import argparse 4 | 5 | import numba 6 | import numpy as np 7 | from xnutrace.tracelog import TraceLog 8 | 9 | 10 | def real_main(args): 11 | tl = TraceLog(args.trace_dir) 12 | if args.jit_info: 13 | tl.jit_info() 14 | return 15 | pcs = tl.based_pcs_for_image(args.image_name) 16 | for base, sz in tl.subregions: 17 | print(f"base: {base:#018x} sz: {sz:#x}") 18 | 19 | 20 | def get_arg_parser() -> argparse.ArgumentParser: 21 | parser = argparse.ArgumentParser(description="find-loops") 22 | parser.add_argument("-t", "--trace-dir", required=True, help="input trace directory") 23 | parser.add_argument("-n", "--image-name", required=True, help="image name to find loops in") 24 | parser.add_argument("-j", "--jit-info", action="store_true", help="dump Numba JIT info") 25 | return parser 26 | 27 | 28 | def main(): 29 | real_main(get_arg_parser().parse_args()) 30 | 31 | 32 | if __name__ == "__main__": 33 | main() 34 | -------------------------------------------------------------------------------- /scripts/atomic_bitvector.py: -------------------------------------------------------------------------------- 1 | def inner_bit_idx(bit_idx, word_bits): 2 | return bit_idx // word_bits * word_bits 3 | 4 | 5 | def inner_word_idx(inner_bit_idx, t_bits): 6 | return inner_bit_idx // t_bits 7 | 8 | 9 | def even_bit_idx(idx, t_bits): 10 | return idx % (2 * t_bits) 11 | 12 | 13 | def odd_bit_idx(idx, t_bits): 14 | return (idx + t_bits) % (2 * t_bits) 15 | 16 | 17 | def bit_idx(idx, word_bits, t_bits): 18 | wsbidx = inner_bit_idx(idx, word_bits) 19 | wswidx = inner_word_idx(wsbidx, t_bits) 20 | eidx = even_bit_idx(idx, t_bits) 21 | oidx = odd_bit_idx(idx, t_bits) 22 | if wswidx % 2 == 0: 23 | bidx = eidx 24 | else: 25 | bidx = oidx 26 | return bidx 27 | 28 | 29 | def bitarray_access_info(idx, word_bits, t_bits): 30 | bidx = idx * word_bits 31 | wsbidx = inner_bit_idx(bidx, word_bits) 32 | wswidx = inner_word_idx(wsbidx, t_bits) 33 | sb_idx = bit_idx(bidx, word_bits, t_bits) 34 | return (wswidx, sb_idx) 35 | 36 | 37 | def atomic_inner_bit_idx(bit_idx, word_bits): 38 | return bit_idx // word_bits * word_bits 39 | 40 | 41 | def atomic_inner_word_idx(inner_bit_idx, t_bits): 42 | return inner_bit_idx // t_bits 43 | 44 | 45 | def atomic_bit_idx(idx, word_bits, t_bits): 46 | wsbidx = atomic_inner_bit_idx(idx, word_bits) 47 | wswidx = atomic_inner_word_idx(wsbidx, t_bits) 48 | return wsbidx 49 | 50 | 51 | def atomic_bitarray_access_info(idx, word_bits, t_bits): 52 | bidx = idx * word_bits 53 | sb_idx = atomic_inner_bit_idx(idx, word_bits) 54 | wswidx = atomic_inner_word_idx(sb_idx, t_bits) 55 | return (wswidx, sb_idx) 56 | -------------------------------------------------------------------------------- /scripts/atomic_bitvector_diagram.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from __future__ import annotations 4 | 5 | import colorsys 6 | import io 7 | 8 | import cairocffi as cairo 9 | from atomic_bitvector import * 10 | from attrs import define 11 | 12 | bit_sz = 32 13 | num_rows = 20 14 | num_bits = 128 15 | canvas_width, canvas_height = (num_bits + 2) * bit_sz, (num_rows + 2) * bit_sz 16 | 17 | 18 | @define 19 | class Color: 20 | h: float 21 | s: float 22 | v: float 23 | 24 | def darken(self, m: float) -> Color: 25 | return Color(self.h, self.s, self.v * m) 26 | 27 | def rgb(self) -> tuple[float, float, float]: 28 | return colorsys.hsv_to_rgb(self.h, self.s, self.v) 29 | 30 | 31 | black = Color(0, 0, 0) 32 | white = Color(0, 0, 1) 33 | grey = white.darken(0.95) 34 | red = Color(0, 0.4, 1) 35 | orange = Color(40 / 360, 0.4, 1) 36 | blue = Color(225 / 360, 0.4, 1) 37 | green = Color(130 / 360, 0.4, 1) 38 | violet = Color(300 / 360, 0.4, 1) 39 | fg = black 40 | bg = white 41 | 42 | 43 | class BitContext(cairo.Context): 44 | def __init__(self, surface: cairo.Surface, bit_sz: int) -> None: 45 | super().__init__(surface) 46 | self.bs = bit_sz 47 | self.select_font_face("Monaco", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_BOLD) 48 | self.set_font_size(self.bs / 2.5) 49 | self.rectangle(0, 0, canvas_width, canvas_height) 50 | self.set_source_rgb(*bg.rgb()) 51 | self.fill() 52 | 53 | def bitrect( 54 | self, bit_idx: int, num_bits: int, row: int, label: str, fill_color: Color = grey 55 | ) -> None: 56 | x, y = (bit_idx * num_bits + 1) * self.bs, (row + 1) * self.bs 57 | w, h = self.bs * num_bits, self.bs 58 | center_x, center_y = x + w / 2, y + h / 2 59 | self.rectangle(x, y, w, h) 60 | self.set_source_rgb(*fill_color.rgb()) 61 | self.fill() 62 | self.rectangle(x, y, w, h) 63 | self.set_source_rgb(*fg.rgb()) 64 | self.stroke() 65 | txb, tyb, tw, th, tdx, tdy = self.text_extents(label) 66 | tx = center_x - tw / 2 - txb 67 | ty = center_y - th / 2 - tyb 68 | self.move_to(tx, ty) 69 | self.set_source_rgb(*fg.rgb()) 70 | self.show_text(label) 71 | 72 | 73 | svgio = io.BytesIO() 74 | surface = cairo.SVGSurface(svgio, canvas_width, canvas_height) 75 | surface.set_document_unit(cairo.SVG_UNIT_USER) 76 | ctx = BitContext(surface, bit_sz) 77 | 78 | word_bits = 14 79 | t_bits = 16 80 | num_words = num_bits // word_bits 81 | 82 | for i in range(num_bits): 83 | # non-atomic 84 | # ctx.bitrect(i, 1, 0, str(i % (2 * t_bits))) 85 | # atomic 86 | ctx.bitrect(i, 1, 0, str(i % (4 * t_bits))) 87 | 88 | # non atomic 89 | # for i, wi in enumerate(((8, orange), (16, blue), (32, green))): 90 | # atomic 91 | for i, wi in enumerate(((8, orange), (16, blue), (32, green), (64, violet))): 92 | wb, wc = wi[0], wi[1] 93 | nw = num_bits // wb 94 | for j in range(nw): 95 | ctx.bitrect(j, wb, i + 1, str(j), wc) 96 | 97 | # NonAtomicBitVector 98 | # for word_idx in range(num_words): 99 | # for i in range(word_bits): 100 | # idx = i + word_idx * word_bits 101 | # ctx.bitrect(idx, 1, 4 + word_idx, str(idx % 32), red) 102 | 103 | # for word_idx in range(num_words): 104 | # for i in range(word_bits): 105 | # idx = i + word_idx * word_bits 106 | # ctx.bitrect(idx, 1, 5 + word_idx, str((idx + 16) % 32), violet) 107 | 108 | # for word_idx in range(num_words): 109 | # for i in range(word_bits): 110 | # idx = i + word_idx * word_bits 111 | # bidx = bit_idx(idx, word_bits, t_bits) 112 | # ctx.bitrect(idx, 1, 6 + word_idx, str(bidx), orange) 113 | # widx, bidx = bitarray_access_info(word_idx, word_bits, t_bits) 114 | # desc = f"[{widx} {bidx}:{bidx + word_bits - 1}]" 115 | # ctx.bitrect(word_idx, word_bits, 7 + word_idx, desc, blue) 116 | 117 | # AtomicBitVector 118 | for word_idx in range(num_words): 119 | for i in range(word_bits): 120 | idx = i + word_idx * word_bits 121 | bidx = atomic_bit_idx(idx, word_bits, t_bits) + i 122 | ctx.bitrect(idx, 1, 6 + word_idx, str(bidx), orange) 123 | widx, bidx = bitarray_access_info(word_idx, word_bits, t_bits) 124 | desc = f"[{widx} {bidx}:{bidx + word_bits - 1}]" 125 | ctx.bitrect(word_idx, word_bits, 7 + word_idx, desc, blue) 126 | 127 | surface.finish() 128 | 129 | with open("atomic-bits.svg", "wb") as svgout: 130 | svgout.write(svgio.getvalue()) 131 | -------------------------------------------------------------------------------- /scripts/atomic_bitvector_simplify.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from cvc5 import * 4 | 5 | 6 | def inner_bit_idx(bit_idx, word_bits): 7 | return bit_idx / word_bits * word_bits 8 | 9 | 10 | def inner_word_idx(inner_bit_idx, t_bits): 11 | return inner_bit_idx / t_bits 12 | 13 | 14 | def even_bit_idx(idx, t_bits): 15 | return idx % (2 * t_bits) 16 | 17 | 18 | def odd_bit_idx(idx, t_bits): 19 | return (idx + t_bits) % (2 * t_bits) 20 | 21 | 22 | def bit_idx(idx, word_bits, t_bits): 23 | wsbidx = inner_bit_idx(idx, word_bits) 24 | wswidx = inner_word_idx(wsbidx, t_bits) 25 | eidx = even_bit_idx(idx, t_bits) 26 | oidx = odd_bit_idx(idx, t_bits) 27 | return If(Extract(0, 0, wswidx) == BitVecVal(1, 1), eidx, oidx) 28 | 29 | 30 | def bitarray_access_info(idx, word_bits, t_bits): 31 | bidx = idx * word_bits 32 | wsbidx = inner_bit_idx(bidx, word_bits) 33 | wswidx = inner_word_idx(wsbidx, t_bits) 34 | sb_idx = bit_idx(bidx, word_bits, t_bits) 35 | return (wswidx, sb_idx) 36 | 37 | 38 | z3bits = 16 39 | 40 | idx = BitVec("idx", z3bits) 41 | word_bits = BitVec("wb", z3bits) 42 | t_bits = BitVecVal(1, z3bits) << BitVec("tbn", z3bits) 43 | 44 | widx, bidx = bitarray_access_info(idx, word_bits, t_bits) 45 | 46 | print(simplify(widx)) 47 | print(simplify(bidx)) 48 | -------------------------------------------------------------------------------- /scripts/instr_bitmask_decode_z3.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import itertools 4 | import sys 5 | 6 | from z3 import * 7 | 8 | z32 = BitVecVal(0, 32) 9 | inst_sym = BitVec("inst_sym", 32) 10 | 11 | ldrp_eq_bm = BitVecVal(0x3F800000, 32) 12 | ldrp_eq_bp = BitVecVal(0x29000000, 32) 13 | ldrp_ne_bm = z32 14 | ldrp_ne_bp = z32 15 | ldrp_example_inst = BitVecVal(0xA9400408, 32) 16 | 17 | ldr_eq_bm = BitVecVal(0x3F000000, 32) 18 | ldr_eq_bp = BitVecVal(0x39000000, 32) 19 | ldr_ne_bm = z32 20 | ldr_ne_bp = z32 21 | ldr_example_inst = BitVecVal(0xF9400020, 32) 22 | 23 | 24 | def inst_match( 25 | inst: BitVecVal, eq_bm: BitVecVal, eq_bp: BitVecVal, ne_bm: BitVecVal, ne_bp: BitVecVal 26 | ) -> BitVecVal: 27 | pos_good = (inst & eq_bm) == eq_bp 28 | neg_good = Or(ne_bm == z32, (inst & ne_bm) != ne_bp) 29 | return pos_good, neg_good, pos_good and neg_good 30 | 31 | 32 | inst = BitVecVal(int.from_bytes(bytes.fromhex(sys.argv[1]), "big"), 32) 33 | print(f"inst_bytes: {inst.as_long():#010x}") 34 | 35 | pg, ng, m = inst_match(ldrp_example_inst, ldrp_eq_bm, ldrp_eq_bp, ldrp_ne_bm, ldrp_ne_bm) 36 | pg, ng, m = simplify(pg), simplify(ng), simplify(m) 37 | print(f"ldrp pg: {pg} ng: {ng} m: {m}") 38 | 39 | pg, ng, m = inst_match(ldr_example_inst, ldr_eq_bm, ldr_eq_bp, ldr_ne_bm, ldr_ne_bm) 40 | pg, ng, m = simplify(pg), simplify(ng), simplify(m) 41 | print(f"ldrp pg: {pg} ng: {ng} m: {m}") 42 | 43 | ldrp_pg, ldrp_ng, ldrp_m = inst_match(inst_sym, ldrp_eq_bm, ldrp_eq_bp, ldrp_ne_bm, ldrp_ne_bm) 44 | ldr_pg, ldr_ng, ldr_m = inst_match(inst_sym, ldr_eq_bm, ldr_eq_bp, ldr_ne_bm, ldr_ne_bm) 45 | ldrp_m = simplify(ldrp_m) 46 | ldr_m = simplify(ldr_m) 47 | ldrp_or_ldr_m = Or(ldrp_m, ldr_m) 48 | ldrp_or_ldr_m = simplify(ldrp_or_ldr_m) 49 | 50 | print(f"ldrp_m: {ldrp_m}") 51 | print(f"ldr_m: {ldr_m}") 52 | print(f"ldrp_or_ldr_m: {ldrp_or_ldr_m}") 53 | 54 | ldrp_example_m = simplify(substitute(ldrp_m, (inst_sym, ldrp_example_inst))) 55 | print(f"ldrp_example_m: {ldrp_example_m}") 56 | 57 | ldr_example_m = simplify(substitute(ldr_m, (inst_sym, ldr_example_inst))) 58 | print(f"ldr_example_m: {ldr_example_m}") 59 | 60 | ldrp_example_m_mul = simplify(substitute(ldrp_or_ldr_m, (inst_sym, ldrp_example_inst))) 61 | print(f"ldrp_example_m_mul: {ldrp_example_m_mul}") 62 | 63 | ldr_example_m_mul = simplify(substitute(ldrp_or_ldr_m, (inst_sym, ldr_example_inst))) 64 | print(f"ldr_example_m_mul: {ldr_example_m_mul}") 65 | -------------------------------------------------------------------------------- /scripts/instr_intersect.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import argparse 4 | 5 | 6 | def real_main(args): 7 | mem_opc = set() 8 | with open(args.mem_opc) as f: 9 | for line in f: 10 | line = line.removeprefix("opc: ") 11 | line = line.removesuffix("\n") 12 | mem_opc.add(line) 13 | prefix_len = len(" 1: ") 14 | seen_opc = set() 15 | with open(args.hist_opc) as f: 16 | for line in f: 17 | line = line[prefix_len:] 18 | line = line.split()[0] 19 | seen_opc.add(line) 20 | seen_mem_opc = mem_opc.intersection(seen_opc) 21 | print("\n".join(sorted(seen_mem_opc))) 22 | 23 | 24 | def get_arg_parser() -> argparse.ArgumentParser: 25 | parser = argparse.ArgumentParser(description="instr-intersect") 26 | parser.add_argument("-m", "--mem-opc", required=True, help="memory opcode file") 27 | parser.add_argument("-H", "--hist-opc", required=True, help="opcode histogram file") 28 | return parser 29 | 30 | 31 | def main(): 32 | real_main(get_arg_parser().parse_args()) 33 | 34 | 35 | if __name__ == "__main__": 36 | main() 37 | -------------------------------------------------------------------------------- /scripts/perfect_hashing.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import random 4 | import sys 5 | 6 | import progressbar 7 | import xxhash 8 | from iteration_utilities import duplicates, unique_everseen 9 | 10 | 11 | class MPH: 12 | def __init__(self, keys: list[int]) -> None: 13 | assert len(set(keys)) == len(keys) 14 | self.keys = keys = sorted(keys) 15 | self.nkeys = nkeys = len(keys) 16 | hashes = {k: self.hash(k) for k in keys} 17 | buckets = [(None, [])] * nkeys 18 | for k, h in hashes.items(): 19 | hmod = h % nkeys 20 | buckets[hmod] = (hmod, buckets[hmod][1] + [k]) 21 | # sort this way to match c++ impl (not matched anymore, but does provide a total ordering here) 22 | sorted_buckets = list(reversed(sorted(buckets, key=lambda t: (len(t[1]), t[0])))) 23 | # print(sorted_buckets[0]) 24 | 25 | salts = [None] * nkeys 26 | slot_used = [False] * nkeys 27 | # for hash, bucket in sorted_buckets: 28 | for hash, bucket in progressbar.progressbar(sorted_buckets): 29 | if len(bucket) > 1: 30 | d = 1 31 | while True: 32 | if d > 4096: 33 | raise RuntimeError("taking too long, sorry") 34 | salted_hashes = [self.hash(k, d) % nkeys for k in bucket] 35 | if len(set(salted_hashes)) != len(salted_hashes): 36 | # collision within salted hashes, try again 37 | d += 1 38 | continue 39 | all_free = all([not slot_used[sh] for sh in salted_hashes]) 40 | if all_free: 41 | for sh in salted_hashes: 42 | slot_used[sh] = True 43 | salts[hash] = d 44 | break 45 | d += 1 46 | elif len(bucket) == 1: 47 | free_idx = slot_used.index(False) 48 | slot_used[free_idx] = True 49 | salts[hash] = -free_idx - 1 50 | self.salts = salts 51 | 52 | @staticmethod 53 | def hash(v: int, d: int = 0) -> int: 54 | return xxhash.xxh64_intdigest(v.to_bytes(8, "little"), d) 55 | 56 | def lookup_idx(self, k: int) -> int: 57 | sv = self.salts[self.hash(k) % self.nkeys] 58 | if sv < 0: 59 | return -sv - 1 60 | else: 61 | return self.hash(k, sv) % self.nkeys 62 | 63 | def check(self): 64 | idxes = sorted([self.lookup_idx(k) for k in self.keys]) 65 | assert idxes[0] == 0 66 | assert idxes[-1] == len(self.keys) - 1 67 | assert len(set(idxes)) == len(self.keys) 68 | assert len(self.salts) == len(self.keys) 69 | 70 | 71 | page_addrs = [] 72 | 73 | for line in open(sys.argv[1]).readlines(): 74 | page_addrs.append(int(line.split()[1], 16)) 75 | 76 | page_addrs = sorted(list(set(page_addrs))) 77 | 78 | # with open("page_addrs.bin", "wb") as f: 79 | # for pa in page_addrs: 80 | # f.write(pa.to_bytes(8, "little")) 81 | 82 | # print(f"len(page_addrs): {len(page_addrs)}") 83 | 84 | mph = MPH(page_addrs) 85 | mph.check() 86 | print(f"max(mph.salts): {max([s for s in mph.salts if s is not None])}") 87 | print(f"{mph.salts.count(None) / mph.nkeys * 100:0.2f}% filled") 88 | 89 | # random.seed(243) 90 | rand_u64 = [random.randint(0, 0xFFFF_FFFF_FFFF_FFFF) for i in range(100_000)] 91 | 92 | # with open("rand_u64_dup_idx_29751.bin", "wb") as f: 93 | # for n in rand_u64: 94 | # f.write(n.to_bytes(8, "little")) 95 | 96 | mph_rand = MPH(rand_u64) 97 | mph_rand.check() 98 | print(f"max(mph_rand.salts): {max([s for s in mph_rand.salts if s is not None])}") 99 | print(f"{mph_rand.salts.count(None) / mph_rand.nkeys * 100:0.2f}% filled") 100 | -------------------------------------------------------------------------------- /scripts/swar-z3.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import itertools 4 | 5 | import z3 6 | 7 | bits = [z3.BitVec(f"b{i}", 1) for i in range(16)] 8 | 9 | ival = z3.Concat(*reversed(bits)) 10 | 11 | print(z3.simplify(ival)) 12 | 13 | oval = z3.Concat( 14 | *itertools.chain(*zip(reversed(bits), [z3.BitVecVal(0, 1) for i in range(len(bits))])) 15 | ) 16 | 17 | print(z3.simplify(oval)) 18 | 19 | ival32 = z3.ZeroExt(48, ival) 20 | 21 | mixed = ival32 22 | mixed = (mixed ^ (mixed << 8)) & z3.BitVecVal(0x00FF_00FF, 64) 23 | mixed = (mixed ^ (mixed << 4)) & z3.BitVecVal(0x0F0F_0F0F, 64) 24 | mixed = (mixed ^ (mixed << 2)) & z3.BitVecVal(0x3333_3333, 64) 25 | mixed = (mixed ^ (mixed << 1)) & z3.BitVecVal(0x5555_5555, 64) 26 | 27 | print(z3.simplify(mixed)) 28 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(yield-loop-step-test yield-loop-step-test.cpp) 2 | target_link_libraries(yield-loop-step-test xnu-trace argparse fmt) 3 | 4 | add_executable(xnu-trace-bench xnu-trace-bench.cpp) 5 | target_link_libraries(xnu-trace-bench xnu-trace argparse benchmark fmt xxhash-xnu-trace) 6 | 7 | add_executable(xnu-trace-nanobench xnu-trace-nanobench.cpp) 8 | target_link_libraries(xnu-trace-nanobench xnu-trace argparse nanobench fmt xxhash-xnu-trace) 9 | 10 | 11 | add_executable(xnu-fast-timeout xnu-fast-timeout.cpp) 12 | target_link_libraries(xnu-fast-timeout xnu-trace fmt) 13 | 14 | add_executable(xnu-trace-mph xnu-trace-mph.cpp) 15 | target_link_libraries(xnu-trace-mph xnu-trace fmt) 16 | 17 | add_executable(hana-101 hana-101.cpp) 18 | target_link_libraries(hana-101 fmt Boost::headers) 19 | 20 | add_executable(vtable-opt vtable-opt.cpp) 21 | target_link_libraries(vtable-opt xnu-trace benchmark) 22 | 23 | add_executable(iterator-proxy iterator-proxy.cpp) 24 | target_link_libraries(iterator-proxy xnu-trace) 25 | 26 | install(TARGETS yield-loop-step-test xnu-trace-bench xnu-trace-nanobench xnu-fast-timeout xnu-trace-mph 27 | RUNTIME DESTINATION bin 28 | ) 29 | -------------------------------------------------------------------------------- /test/hana-101.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | struct NumberBase { 7 | virtual ~NumberBase() {} 8 | virtual void print(int addend) const = 0; 9 | }; 10 | 11 | template struct NumberDerived : NumberBase { 12 | void print(int addend) const final override { 13 | printf("NumberDerived is %d\n", Num + addend); 14 | } 15 | }; 16 | 17 | std::unique_ptr NumberFactory(int num) { 18 | assert(num >= 0 && num < 4); 19 | switch (num) { 20 | case 0: 21 | return std::make_unique>(); 22 | case 1: 23 | return std::make_unique>(); 24 | case 2: 25 | return std::make_unique>(); 26 | case 3: 27 | return std::make_unique>(); 28 | default: 29 | return {}; 30 | } 31 | } 32 | 33 | struct Number { 34 | Number(int num) : m_bv(NumberFactory(num)) {} 35 | __attribute__((always_inline)) void print(int addend) const { 36 | m_bv->print(addend); 37 | } 38 | const std::unique_ptr m_bv; 39 | }; 40 | 41 | __attribute__((noinline)) void call_print_bad(const Number &number) { 42 | for (int i = 0; i < 4; ++i) { 43 | number.print(i); 44 | } 45 | } 46 | 47 | __attribute__((noinline)) void call_print_good(const Number &number) { 48 | const auto bv_impl = number.m_bv.get(); 49 | for (int i = 0; i < 4; ++i) { 50 | bv_impl->print(i); 51 | } 52 | } 53 | 54 | int main(int argc, const char **argv) { 55 | assert(argc == 2); 56 | int n = atoi(argv[1]); 57 | 58 | const auto num = Number(n); 59 | call_print_bad(num); 60 | call_print_good(num); 61 | 62 | return 0; 63 | } 64 | -------------------------------------------------------------------------------- /test/iterator-proxy.cpp: -------------------------------------------------------------------------------- 1 | #include "xnu-trace/BitVector.h" 2 | 3 | XNUTRACE_NOINLINE 4 | uint64_t psum_get(NonAtomicBitVectorImpl<17> &bv, size_t num) { 5 | uint64_t sum = 0; 6 | for (size_t i = 0; i < num; ++i) { 7 | sum += bv.get(i); 8 | } 9 | return sum; 10 | } 11 | 12 | XNUTRACE_NOINLINE 13 | uint64_t psum_op(NonAtomicBitVectorImpl<17> &bv, size_t num) { 14 | uint64_t sum = 0; 15 | for (size_t i = 0; i < num; ++i) { 16 | sum += bv[i]; 17 | } 18 | return sum; 19 | } 20 | 21 | XNUTRACE_NOINLINE 22 | uint64_t psum_get_te(BitVector<> &bv, size_t num) { 23 | uint64_t sum = 0; 24 | for (size_t i = 0; i < num; ++i) { 25 | sum += bv.get(i); 26 | } 27 | return sum; 28 | } 29 | 30 | XNUTRACE_NOINLINE 31 | uint64_t psum_op_te(BitVector<> &bv, size_t num) { 32 | uint64_t sum = 0; 33 | for (size_t i = 0; i < num; ++i) { 34 | sum += bv[i]; 35 | } 36 | return sum; 37 | } 38 | 39 | int main() { 40 | auto bv = NonAtomicBitVectorImpl<17>(8); 41 | return psum_get(bv, 5) + psum_op(bv, 5) + psum_get_te(bv, 5) + psum_op_te(bv, 5); 42 | } 43 | -------------------------------------------------------------------------------- /test/page_addrs.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jevinskie/xnu-trace/f6f0d03440fc381246a13ccaae75f67d6ffa0fac/test/page_addrs.bin -------------------------------------------------------------------------------- /test/page_addrs.txt.zst: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jevinskie/xnu-trace/f6f0d03440fc381246a13ccaae75f67d6ffa0fac/test/page_addrs.txt.zst -------------------------------------------------------------------------------- /test/rand_u64_dup_idx_29751.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jevinskie/xnu-trace/f6f0d03440fc381246a13ccaae75f67d6ffa0fac/test/rand_u64_dup_idx_29751.bin -------------------------------------------------------------------------------- /test/vtable-opt.cpp: -------------------------------------------------------------------------------- 1 | #include "xnu-trace/xnu-trace.h" 2 | 3 | #include 4 | 5 | #include 6 | 7 | static uint64_t hash_n(uint8_t nbits, uint64_t val) { 8 | return xxhash3_64::hash(val) & xnutrace::BitVector::bit_mask(0, nbits); 9 | } 10 | 11 | __attribute__((noinline)) static uint64_t 12 | test_non_atomic_bitvector(const std::unique_ptr> &bv, size_t sz) { 13 | size_t i = 0; 14 | uint64_t res = 0; 15 | for (size_t i = 0; i < 512 * 1024 * 1024; ++i) { 16 | res ^= bv->get(i % sz); 17 | } 18 | return res; 19 | } 20 | 21 | int main() { 22 | constexpr uint8_t nbits = 31; 23 | constexpr size_t sz = 128 * 1024 * 1024 / sizeof(uint32_t); 24 | auto bv = BitVectorFactory<>(nbits, sz); 25 | for (size_t i = 0; i < sz; ++i) { 26 | bv->set(i, hash_n(i, nbits)); 27 | } 28 | const auto res = test_non_atomic_bitvector(bv, sz); 29 | return (int)res; 30 | } 31 | -------------------------------------------------------------------------------- /test/xnu-fast-timeout.cpp: -------------------------------------------------------------------------------- 1 | #include "xnu-trace/xnu-trace.h" 2 | 3 | #undef NDEBUG 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | int main(void) { 10 | 11 | auto timeout_done = XNUFastTimeout(1'000'000'000); 12 | for (int i = 0; i < 25 && !timeout_done.done(); ++i) { 13 | fmt::print("done xnu_commpage_time_atus(): {:d}\n", xnu_commpage_time_atus()); 14 | usleep(250'000); 15 | } 16 | 17 | auto timeout_check = XNUFastTimeout(1'000'000'000, [&]() { 18 | assert(!"timed out"); 19 | }); 20 | for (int i = 0; i < 25; ++i) { 21 | fmt::print("check xnu_commpage_time_atus(): {:d}\n", xnu_commpage_time_atus()); 22 | usleep(250'000); 23 | timeout_check.check(); 24 | } 25 | return 0; 26 | } 27 | -------------------------------------------------------------------------------- /test/xnu-trace-mph.cpp: -------------------------------------------------------------------------------- 1 | #include "xnu-trace/xnu-trace.h" 2 | 3 | #undef NDEBUG 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | int main(int argc, const char **argv) { 11 | if (argc != 2) { 12 | fmt::print("usage: {:s} \n"); 13 | return -1; 14 | } 15 | 16 | Signpost("mph", "read file start", true, 17 | fmt::format("time: {:%H:%M:%S}", fmt::localtime(std::time(nullptr)))); 18 | 19 | auto load_sp = Signpost("mph", "loading"); 20 | load_sp.start(); 21 | const auto buf = read_file(argv[1]); 22 | const auto raw_buf = (uint64_t *)buf.data(); 23 | const auto nkeys = buf.size() / sizeof(uint64_t); 24 | std::vector keys; 25 | keys.reserve(nkeys); 26 | for (size_t i = 0; i < nkeys; ++i) { 27 | keys.emplace_back(raw_buf[i]); 28 | } 29 | load_sp.end([&](uint64_t ns) { 30 | return fmt::format("{:0.3f} bytes / nanosecond", buf.size() / (double)ns); 31 | }); 32 | 33 | auto build_sp = Signpost("mph", "building"); 34 | build_sp.start(); 35 | auto mph = MinimalPerfectHash{}; 36 | mph.build(keys); 37 | build_sp.end(); 38 | 39 | return 0; 40 | } 41 | -------------------------------------------------------------------------------- /test/xnu-trace-nanobench.cpp: -------------------------------------------------------------------------------- 1 | #include "xnu-trace/xnu-trace.h" 2 | 3 | #undef NDEBUG 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | #define ANKERL_NANOBENCH_IMPLEMENT 12 | #include 13 | #include 14 | #define XXH_INLINE_ALL 15 | #include 16 | 17 | namespace fs = std::filesystem; 18 | using namespace ankerl; 19 | 20 | static void BM_lookup_inst(const TraceLog &trace) { 21 | const auto ®ions = trace.macho_regions(); 22 | std::vector addrs; 23 | for (const auto ®ion : regions.regions()) { 24 | addrs.emplace_back(region.base + 4); 25 | } 26 | const auto num_addrs = addrs.size(); 27 | const auto *addrs_buf = addrs.data(); 28 | 29 | size_t i = 0; 30 | 31 | nanobench::Bench().run("MachORegions::lookup_inst even dist", [&]() { 32 | nanobench::doNotOptimizeAway(regions.lookup_inst(addrs_buf[i % num_addrs])); 33 | ++i; 34 | }); 35 | } 36 | 37 | static void BM_lookup_inst_from_trace(const TraceLog &trace) { 38 | const auto ®ions = trace.macho_regions(); 39 | std::vector addrs; 40 | addrs.resize(trace.num_inst()); 41 | size_t i = 0; 42 | for (const auto &[tid, log] : trace.parsed_logs()) { 43 | for (const auto msg : log) { 44 | // FIXME 45 | // addrs[i] = msg.pc; 46 | ++i; 47 | } 48 | } 49 | const auto num_addrs = addrs.size(); 50 | const auto *addrs_buf = addrs.data(); 51 | 52 | i = 0; 53 | nanobench::Bench().run("MachORegions::lookup_inst trace dist", [&]() { 54 | nanobench::doNotOptimizeAway(regions.lookup_inst(addrs_buf[i % num_addrs])); 55 | ++i; 56 | }); 57 | } 58 | 59 | static void BM_histogram_add(const TraceLog &trace) { 60 | const auto ®ions = trace.macho_regions(); 61 | std::vector instrs; 62 | instrs.resize(trace.num_inst()); 63 | size_t i = 0; 64 | for (const auto &[tid, log] : trace.parsed_logs()) { 65 | for (const auto msg : log) { 66 | // FIXME 67 | // instrs[i] = regions.lookup_inst(msg.pc); 68 | ++i; 69 | } 70 | } 71 | const auto num_instrs = instrs.size(); 72 | const auto *instrs_buf = instrs.data(); 73 | 74 | ARM64InstrHistogram hist; 75 | i = 0; 76 | nanobench::Bench().run("ARM64InstrHistogram::add", [&]() { 77 | hist.add(instrs_buf[i % num_instrs]); 78 | ++i; 79 | }); 80 | } 81 | 82 | static void BM_xxhash64() { 83 | uint64_t i = 0; 84 | nanobench::Bench().run("XXH64", [&]() { 85 | nanobench::doNotOptimizeAway(xxhash_64::hash(i, i * 7)); 86 | ++i; 87 | }); 88 | } 89 | 90 | static void BM_xxhash3_64() { 91 | uint64_t i = 0; 92 | nanobench::Bench().run("XXH3_64", [&]() { 93 | nanobench::doNotOptimizeAway(xxhash3_64::hash(i, i * 7)); 94 | ++i; 95 | }); 96 | } 97 | 98 | static void BM_jevhash_64() { 99 | uint64_t i = 0; 100 | nanobench::Bench().run("jevhash_64", [&]() { 101 | nanobench::doNotOptimizeAway(jevhash_64::hash(i, i * 7)); 102 | ++i; 103 | }); 104 | } 105 | 106 | static void BM_jevhash_32() { 107 | uint64_t i = 0; 108 | nanobench::Bench().run("jevhash_32", [&]() { 109 | nanobench::doNotOptimizeAway(jevhash_32::hash(i, i * 7)); 110 | ++i; 111 | }); 112 | } 113 | 114 | static void BM_xnu_commpage_time_seconds() { 115 | nanobench::Bench().run("xnu_commpage_time_seconds", [&]() { 116 | nanobench::doNotOptimizeAway(xnu_commpage_time_seconds()); 117 | }); 118 | } 119 | 120 | static void BM_mach_absolute_time() { 121 | nanobench::Bench().run("mach_absolute_time", [&]() { 122 | nanobench::doNotOptimizeAway(mach_absolute_time()); 123 | }); 124 | } 125 | 126 | static void BM_mach_approximate_time() { 127 | nanobench::Bench().run("mach_approximate_time", [&]() { 128 | nanobench::doNotOptimizeAway(mach_approximate_time()); 129 | }); 130 | } 131 | 132 | int main(void) { 133 | const auto trace = TraceLog("harness.bundle"); 134 | 135 | BM_lookup_inst(trace); 136 | BM_lookup_inst_from_trace(trace); 137 | BM_histogram_add(trace); 138 | BM_xxhash64(); 139 | BM_xxhash3_64(); 140 | BM_jevhash_64(); 141 | BM_jevhash_32(); 142 | BM_xnu_commpage_time_seconds(); 143 | BM_mach_absolute_time(); 144 | BM_mach_approximate_time(); 145 | 146 | return 0; 147 | } 148 | -------------------------------------------------------------------------------- /test/yield-loop-step-test.cpp: -------------------------------------------------------------------------------- 1 | #undef NDEBUG 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | 15 | #include "xnu-trace/xnu-trace.h" 16 | 17 | static volatile bool should_stop; 18 | 19 | static void sig_handler(int sig_id) { 20 | (void)sig_id; 21 | should_stop = true; 22 | } 23 | 24 | static void setup_sig_handler() { 25 | struct sigaction sa; 26 | sa.sa_handler = sig_handler; 27 | sigfillset(&sa.sa_mask); 28 | sigaction(SIGINT, &sa, nullptr); 29 | } 30 | 31 | __attribute__((noinline)) static void null_deref() { 32 | int *pnull = nullptr; 33 | *pnull = 1337; 34 | } 35 | 36 | int main(int argc, const char **argv) { 37 | argparse::ArgumentParser parser(getprogname()); 38 | parser.add_argument("-c", "--crash-on-attach") 39 | .help("crash on attachment") 40 | .default_value(false) 41 | .implicit_value(true); 42 | parser.add_argument("-f", "--forever") 43 | .help("run forever") 44 | .default_value(false) 45 | .implicit_value(true); 46 | parser.add_argument("-F", "--frida-stalker") 47 | .help("frida stalker mode") 48 | .default_value(false) 49 | .implicit_value(true); 50 | 51 | try { 52 | parser.parse_args(argc, argv); 53 | } catch (const std::runtime_error &err) { 54 | fmt::print(stderr, "Error parsing arguments: {:s}\n", err.what()); 55 | return -1; 56 | } 57 | 58 | bool do_pipe = false; 59 | if (fcntl(PIPE_TRACER2TARGET_FD, F_GETFD) != -1 && 60 | fcntl(PIPE_TARGET2TRACER_FD, F_GETFD) != -1) { 61 | fmt::print("target utilizing pipe ctrl\n"); 62 | do_pipe = true; 63 | } 64 | 65 | const auto crash_on_attach = parser["--crash-on-attach"] == true; 66 | const auto forever = parser["--forever"] == true; 67 | const auto do_stalker = parser["--frida-stalker"] == true; 68 | 69 | uint64_t num_yields = 0; 70 | const auto task_self = mach_task_self(); 71 | fmt::print("yield-loop-step-test begin\n"); 72 | setup_sig_handler(); 73 | should_stop = false; 74 | 75 | if (do_pipe) { 76 | pipe_set_single_step(true); 77 | } 78 | 79 | std::unique_ptr stalker; 80 | if (do_stalker) { 81 | stalker = std::make_unique("yield-loop-step-test.bundle", true, 10, true); 82 | fmt::print("stalking start\n"); 83 | stalker->follow(); 84 | } 85 | 86 | while (!should_stop) { 87 | ++num_yields; 88 | // swtch_pri(0); 89 | fmt::print("num_yields: {:d}\n", num_yields); 90 | usleep(1'000'000); 91 | if (crash_on_attach && get_task_for_pid_count(task_self)) { 92 | null_deref(); 93 | } 94 | if (num_yields > 4 && !forever) { 95 | should_stop = 1; 96 | fmt::print("stopping from limit\n"); 97 | } 98 | } 99 | 100 | if (do_pipe) { 101 | fmt::print("writing stop to pipe\n"); 102 | pipe_set_single_step(false); 103 | fmt::print("wrote stop to pipe\n"); 104 | } 105 | 106 | if (stalker) { 107 | stalker->unfollow(); 108 | fmt::print("stalking end\n"); 109 | stalker.reset(); 110 | } 111 | 112 | fmt::print("final num_yields: {:d}\n", num_yields); 113 | fmt::print("yield-loop-step-test end\n"); 114 | return 0; 115 | } 116 | -------------------------------------------------------------------------------- /tools/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(capstone-arm64-enumerate-mem-insn) 2 | add_subdirectory(spawn-no-aslr) 3 | add_subdirectory(xnu-trace-log-util) 4 | add_subdirectory(xnu-trace-single-step-runner) 5 | add_subdirectory(xnu-trace-compressed-file-util) 6 | add_subdirectory(xnu-trace-render) 7 | -------------------------------------------------------------------------------- /tools/XNUTracer/XNUTracer.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /tools/XNUTracer/XNUTracer.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /tools/XNUTracer/XNUTracer/AppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface AppDelegate : NSObject 4 | 5 | @end 6 | -------------------------------------------------------------------------------- /tools/XNUTracer/XNUTracer/AppDelegate.m: -------------------------------------------------------------------------------- 1 | #import "AppDelegate.h" 2 | 3 | @interface AppDelegate () 4 | 5 | @end 6 | 7 | @implementation AppDelegate 8 | 9 | - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { 10 | // Insert code here to initialize your application 11 | } 12 | 13 | - (void)applicationWillTerminate:(NSNotification *)aNotification { 14 | // Insert code here to tear down your application 15 | } 16 | 17 | - (BOOL)applicationSupportsSecureRestorableState:(NSApplication *)app { 18 | return YES; 19 | } 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /tools/XNUTracer/XNUTracer/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /tools/XNUTracer/XNUTracer/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "scale" : "1x", 6 | "size" : "16x16" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "scale" : "2x", 11 | "size" : "16x16" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "scale" : "1x", 16 | "size" : "32x32" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "scale" : "2x", 21 | "size" : "32x32" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "scale" : "1x", 26 | "size" : "128x128" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "scale" : "2x", 31 | "size" : "128x128" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "scale" : "1x", 36 | "size" : "256x256" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "scale" : "2x", 41 | "size" : "256x256" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "scale" : "1x", 46 | "size" : "512x512" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "scale" : "2x", 51 | "size" : "512x512" 52 | } 53 | ], 54 | "info" : { 55 | "author" : "xcode", 56 | "version" : 1 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /tools/XNUTracer/XNUTracer/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /tools/XNUTracer/XNUTracer/ViewController.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface ViewController : NSViewController 4 | 5 | @end 6 | -------------------------------------------------------------------------------- /tools/XNUTracer/XNUTracer/ViewController.m: -------------------------------------------------------------------------------- 1 | #import "ViewController.h" 2 | 3 | @implementation ViewController 4 | 5 | - (void)viewDidLoad { 6 | [super viewDidLoad]; 7 | 8 | // Do any additional setup after loading the view. 9 | } 10 | 11 | - (void)setRepresentedObject:(id)representedObject { 12 | [super setRepresentedObject:representedObject]; 13 | 14 | // Update the view, if already loaded. 15 | } 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /tools/XNUTracer/XNUTracer/XNUTracer.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /tools/XNUTracer/XNUTracer/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | int main(int argc, const char *argv[]) { 4 | @autoreleasepool { 5 | // Setup code that might create autoreleased objects goes here. 6 | } 7 | return NSApplicationMain(argc, argv); 8 | } 9 | -------------------------------------------------------------------------------- /tools/capstone-arm64-enumerate-mem-insn/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(capstone-arm64-enumerate-mem-insn capstone-arm64-enumerate-mem-insn.cpp) 2 | 3 | target_link_libraries(capstone-arm64-enumerate-mem-insn PRIVATE bn-arm64-disasm fmt frida-gum) 4 | target_compile_options(capstone-arm64-enumerate-mem-insn PRIVATE -Wall -Wextra -Wpedantic) 5 | 6 | install(TARGETS capstone-arm64-enumerate-mem-insn 7 | RUNTIME DESTINATION bin 8 | ) 9 | -------------------------------------------------------------------------------- /tools/capstone-arm64-enumerate-mem-insn/capstone-arm64-enumerate-mem-insn.cpp: -------------------------------------------------------------------------------- 1 | #undef NDEBUG 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | int main() { 12 | csh cs_handle; 13 | assert(cs_open(CS_ARCH_ARM64, CS_MODE_LITTLE_ENDIAN, &cs_handle) == CS_ERR_OK); 14 | cs_option(cs_handle, CS_OPT_DETAIL, CS_OPT_ON); 15 | std::set mem_opc; 16 | for (uint64_t instr64 = 0; instr64 <= UINT32_MAX; ++instr64) { 17 | uint32_t instr = (uint32_t)instr64; 18 | Instruction inst_repr; 19 | if (aarch64_decompose(instr, &inst_repr, 0) != DECODE_STATUS_OK) { 20 | continue; 21 | } 22 | cs_insn *cs_instr; 23 | const auto count = 24 | cs_disasm(cs_handle, (const uint8_t *)&instr, sizeof(instr), 0, 0, &cs_instr); 25 | assert(count == 0 || count == 1); 26 | if (count == 0) { 27 | // diff between binary ninja and capstone 28 | cs_free(cs_instr, count); 29 | continue; 30 | } 31 | bool is_mem = false; 32 | cs_arm64 *arm64 = &cs_instr->detail->arm64; 33 | for (int i = 0; i < arm64->op_count; ++i) { 34 | cs_arm64_op *op = &arm64->operands[i]; 35 | if (op->type == ARM64_OP_MEM) { 36 | is_mem = true; 37 | break; 38 | } 39 | } 40 | cs_free(cs_instr, count); 41 | if (is_mem) { 42 | mem_opc.emplace(get_operation(&inst_repr)); 43 | } 44 | } 45 | for (const auto &opc : mem_opc) { 46 | fmt::print("opc: {:s}\n", opc); 47 | } 48 | return 0; 49 | } 50 | -------------------------------------------------------------------------------- /tools/spawn-no-aslr/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(spawn-no-aslr spawn-no-aslr.cpp) 2 | 3 | target_compile_options(spawn-no-aslr PRIVATE -Wall -Wextra -Wpedantic) 4 | 5 | install(TARGETS spawn-no-aslr 6 | RUNTIME DESTINATION bin 7 | ) 8 | -------------------------------------------------------------------------------- /tools/spawn-no-aslr/spawn-no-aslr.cpp: -------------------------------------------------------------------------------- 1 | #undef NDEBUG 2 | #include 3 | #include 4 | #include 5 | 6 | #ifndef _POSIX_SPAWN_DISABLE_ASLR 7 | #define _POSIX_SPAWN_DISABLE_ASLR 0x0100 8 | #endif 9 | 10 | extern "C" char **environ; 11 | 12 | int main(int argc, char **argv) { 13 | assert(argc >= 2); 14 | 15 | posix_spawnattr_t attr; 16 | assert(!posix_spawnattr_init(&attr)); 17 | assert(!posix_spawnattr_setflags(&attr, _POSIX_SPAWN_DISABLE_ASLR)); 18 | 19 | pid_t pid; 20 | assert(!posix_spawnp(&pid, argv[1], nullptr, &attr, &argv[1], environ)); 21 | assert(!posix_spawnattr_destroy(&attr)); 22 | int status; 23 | while (waitpid(pid, &status, 0) >= 0) { 24 | if (WIFEXITED(status)) { 25 | return WEXITSTATUS(status); 26 | } 27 | return -41; 28 | } 29 | return -42; 30 | } 31 | -------------------------------------------------------------------------------- /tools/xnu-trace-compressed-file-util/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(xnu-trace-compressed-file-util xnu-trace-compressed-file-util.cpp) 2 | 3 | target_link_libraries(xnu-trace-compressed-file-util xnu-trace argparse fmt) 4 | target_compile_options(xnu-trace-compressed-file-util PRIVATE -Wall -Wextra -Wpedantic) 5 | 6 | install(TARGETS xnu-trace-compressed-file-util 7 | RUNTIME DESTINATION bin 8 | ) 9 | -------------------------------------------------------------------------------- /tools/xnu-trace-compressed-file-util/xnu-trace-compressed-file-util.cpp: -------------------------------------------------------------------------------- 1 | #include "xnu-trace/xnu-trace.h" 2 | 3 | #undef NDEBUG 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | namespace fs = std::filesystem; 12 | 13 | int main(int argc, const char **argv) { 14 | argparse::ArgumentParser parser(getprogname()); 15 | parser.add_argument("-i", "--input").required().help("input CompressedFile path"); 16 | parser.add_argument("-o", "--output").required().help("output path"); 17 | parser.add_argument("-H", "--header") 18 | .default_value(false) 19 | .implicit_value(true) 20 | .help("output header instead of body"); 21 | 22 | try { 23 | parser.parse_args(argc, argv); 24 | } catch (const std::runtime_error &err) { 25 | fmt::print(stderr, "Error parsing arguments: {:s}\n", err.what()); 26 | return -1; 27 | } 28 | 29 | const fs::path in_path{parser.get("--input")}; 30 | const auto out_path{parser.get("--output")}; 31 | const bool output_header{parser["--header"] == true}; 32 | 33 | CompressedFileRawRead cf{in_path}; 34 | 35 | if (output_header) { 36 | write_file(out_path, cf.header_buf().data(), cf.header_buf().size()); 37 | } else { 38 | const auto buf = cf.read(); 39 | write_file(out_path, buf.data(), buf.size()); 40 | } 41 | 42 | return 0; 43 | } 44 | -------------------------------------------------------------------------------- /tools/xnu-trace-log-util/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(xnu-trace-log-util xnu-trace-log-util.cpp) 2 | 3 | target_link_libraries(xnu-trace-log-util xnu-trace argparse fmt benchmark) 4 | target_compile_options(xnu-trace-log-util PRIVATE -Wall -Wextra -Wpedantic) 5 | 6 | install(TARGETS xnu-trace-log-util 7 | RUNTIME DESTINATION bin 8 | ) 9 | -------------------------------------------------------------------------------- /tools/xnu-trace-render/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(xnu-trace-render xnu-trace-render.cpp) 2 | 3 | target_link_libraries(xnu-trace-render xnu-trace argparse fmt) 4 | target_compile_options(xnu-trace-render PRIVATE -Wall -Wextra -Wpedantic) 5 | 6 | install(TARGETS xnu-trace-render 7 | RUNTIME DESTINATION bin 8 | ) 9 | -------------------------------------------------------------------------------- /tools/xnu-trace-render/xnu-trace-render.cpp: -------------------------------------------------------------------------------- 1 | #include "xnu-trace/xnu-trace.h" 2 | 3 | #undef NDEBUG 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | namespace fs = std::filesystem; 12 | 13 | int main(int argc, const char **argv) { 14 | argparse::ArgumentParser parser(getprogname()); 15 | 16 | try { 17 | parser.parse_args(argc, argv); 18 | } catch (const std::runtime_error &err) { 19 | fmt::print(stderr, "Error parsing arguments: {:s}\n", err.what()); 20 | return -1; 21 | } 22 | 23 | return 0; 24 | } 25 | -------------------------------------------------------------------------------- /tools/xnu-trace-single-step-runner/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(xnu-trace-single-step-runner xnu-trace-single-step-runner.cpp) 2 | 3 | target_link_libraries(xnu-trace-single-step-runner xnu-trace argparse fmt) 4 | target_compile_options(xnu-trace-single-step-runner PRIVATE -Wall -Wextra -Wpedantic) 5 | 6 | add_custom_command(TARGET xnu-trace-single-step-runner 7 | POST_BUILD 8 | COMMAND codesign -s - -o linker-signed --entitlements ${CMAKE_CURRENT_SOURCE_DIR}/ent.xml $ 9 | ) 10 | 11 | set_target_properties(xnu-trace-single-step-runner PROPERTIES 12 | XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED "NO" 13 | # XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "" 14 | ) 15 | 16 | install(TARGETS xnu-trace-single-step-runner 17 | RUNTIME DESTINATION bin 18 | ) 19 | -------------------------------------------------------------------------------- /tools/xnu-trace-single-step-runner/ent.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.get-task-allow 6 | 7 | com.apple.security.cs.debugger 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /tools/xnu-trace-single-step-runner/xnu-trace-single-step-runner.cpp: -------------------------------------------------------------------------------- 1 | #include "xnu-trace/xnu-trace.h" 2 | 3 | #undef NDEBUG 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | 16 | #include 17 | #include 18 | 19 | int main(int argc, const char **argv) { 20 | argparse::ArgumentParser parser(getprogname()); 21 | parser.add_argument("-s", "--spawn") 22 | .default_value(false) 23 | .implicit_value(true) 24 | .help("spawn process"); 25 | parser.add_argument("-p", "--pipe") 26 | .default_value(false) 27 | .implicit_value(true) 28 | .help("control trace using child pipe"); 29 | parser.add_argument("-a", "--attach") 30 | .default_value(false) 31 | .implicit_value(true) 32 | .help("attach to process"); 33 | parser.add_argument("-A", "--no-aslr") 34 | .default_value(false) 35 | .implicit_value(true) 36 | .help("disable ASLR (spawned processes only)"); 37 | parser.add_argument("-y", "--symbolicate") 38 | .default_value(false) 39 | .implicit_value(true) 40 | .help("record symbol information"); 41 | parser.add_argument("-t", "--trace-file").required().help("output trace file path"); 42 | parser.add_argument("-c", "--compression-level") 43 | .scan<'i', int>() 44 | .default_value(10) 45 | .help("zstd compression level"); 46 | parser.add_argument("-S", "--stream") 47 | .default_value(false) 48 | .implicit_value(true) 49 | .help("stream to disk"); 50 | parser.add_argument("spawn-args").remaining().help("spawn executable path and arguments"); 51 | 52 | try { 53 | parser.parse_args(argc, argv); 54 | } catch (const std::runtime_error &err) { 55 | fmt::print(stderr, "Error parsing arguments: {:s}\n", err.what()); 56 | return -1; 57 | } 58 | 59 | const auto do_spawn = parser["--spawn"] == true; 60 | const auto do_attach = parser["--attach"] == true; 61 | const auto disable_aslr = parser["--no-aslr"] == true; 62 | const auto do_pipe = parser["--pipe"] == true; 63 | XNUTracer::opts opts{.symbolicate = parser["--symbolicate"] == true, 64 | .compression_level = parser.get("--compression-level"), 65 | .stream = parser["--stream"] == true}; 66 | if (const auto arg = parser.present("--trace-file")) { 67 | opts.trace_path = *arg; 68 | } 69 | 70 | if ((!do_spawn && !do_attach) || (do_spawn && do_attach)) { 71 | fmt::print(stderr, "Must specify one of --spawn or --attach\n"); 72 | return -1; 73 | } 74 | 75 | if (do_attach && disable_aslr) { 76 | fmt::print(stderr, "Can't disable ASLR on attached processes\n"); 77 | return -1; 78 | } 79 | 80 | if (do_attach && do_pipe) { 81 | fmt::print(stderr, "Can't control via pipes on attached processes\n"); 82 | return -1; 83 | } 84 | 85 | fmt::print(stderr, "xnu-trace-util begin self PID: {:d}\n", getpid()); 86 | 87 | std::unique_ptr tracer; 88 | 89 | if (do_attach) { 90 | const auto target_name = *parser.present("--attach"); 91 | tracer = std::make_unique(target_name, opts); 92 | } else if (do_spawn) { 93 | const auto spawn_args = parser.get>("spawn-args"); 94 | tracer = std::make_unique(spawn_args, do_pipe, disable_aslr, opts); 95 | } else { 96 | assert(!"nothing to do"); 97 | } 98 | 99 | XNUTracer *tracer_raw = tracer.get(); 100 | 101 | const auto queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 102 | assert(queue); 103 | 104 | const auto signal_source = 105 | dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGINT, 0, queue); 106 | assert(signal_source); 107 | dispatch_source_set_event_handler(signal_source, ^{ 108 | delete tracer_raw; 109 | exit(0); 110 | }); 111 | dispatch_resume(signal_source); 112 | 113 | if (do_pipe) { 114 | const auto pipe_source = tracer->pipe_dispatch_source(); 115 | dispatch_resume(pipe_source); 116 | tracer->set_single_step(false); 117 | } 118 | 119 | const auto breakpoint_exc_source = tracer->breakpoint_exception_port_dispath_source(); 120 | dispatch_resume(breakpoint_exc_source); 121 | 122 | const auto proc_source = tracer->proc_dispath_source(); 123 | dispatch_source_set_event_handler(proc_source, ^{ 124 | delete tracer_raw; 125 | exit(0); 126 | }); 127 | dispatch_resume(proc_source); 128 | 129 | tracer->resume(); 130 | 131 | dispatch_main(); 132 | 133 | fmt::print(stderr, "xnu-trace-util end\n"); 134 | return 0; 135 | } 136 | -------------------------------------------------------------------------------- /unit-tests/ARM64Disassembler.cpp: -------------------------------------------------------------------------------- 1 | #include "xnu-trace/xnu-trace.h" 2 | 3 | #include 4 | #include 5 | 6 | #define TS "[ARM64Disassembler]" 7 | -------------------------------------------------------------------------------- /unit-tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(XNUTRACE_UNIT_TEST_SRC 2 | ARM64Disassembler.cpp 3 | BitVector.cpp 4 | EliasFano.cpp 5 | MinimalPerfectHash.cpp 6 | RankSelect.cpp 7 | memmem-chunking.cpp 8 | ) 9 | 10 | add_executable(xnu-trace-unit-tests ${XNUTRACE_UNIT_TEST_SRC}) 11 | target_link_libraries(xnu-trace-unit-tests PRIVATE xnu-trace fmt range-v3 Catch2 Catch2WithMain) 12 | -------------------------------------------------------------------------------- /unit-tests/EliasFano.cpp: -------------------------------------------------------------------------------- 1 | #include "xnu-trace/xnu-trace.h" 2 | 3 | #include 4 | #include 5 | 6 | #define TS "[EliasFano]" 7 | 8 | TEST_CASE("build", TS) { 9 | const auto seq = get_random_sorted_unique_scalars(16, 16); 10 | fmt::print("seq: {:d}\n", fmt::join(seq, ", ")); 11 | EliasFanoSequence()> ef(std::span{seq}); 12 | } 13 | -------------------------------------------------------------------------------- /unit-tests/MinimalPerfectHash.cpp: -------------------------------------------------------------------------------- 1 | #include "xnu-trace/xnu-trace.h" 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #define TS "[MinimalPerfectHash]" 9 | 10 | namespace fs = std::filesystem; 11 | 12 | template 13 | static void check_mph(const std::vector &keys, const MinimalPerfectHash &mph) { 14 | std::vector idxes; 15 | idxes.reserve(keys.size()); 16 | for (const auto &k : keys) { 17 | idxes.emplace_back(mph(k)); 18 | } 19 | std::sort(idxes.begin(), idxes.end()); 20 | idxes.erase(std::unique(idxes.begin(), idxes.end()), idxes.end()); 21 | REQUIRE(idxes.size() == keys.size()); 22 | REQUIRE(idxes[0] == 0); 23 | REQUIRE(idxes[idxes.size() - 1] == idxes.size() - 1); 24 | } 25 | 26 | TEST_CASE("build", TS) { 27 | const auto keys = get_random_scalars(100'000); 28 | MinimalPerfectHash mph; 29 | mph.build(keys); 30 | } 31 | 32 | TEST_CASE("check", TS) { 33 | const auto keys = get_random_scalars(100'000); 34 | MinimalPerfectHash mph; 35 | mph.build(keys); 36 | check_mph(keys, mph); 37 | } 38 | 39 | TEST_CASE("check_page_addrs", TS) { 40 | const auto keys = read_numbers_from_file( 41 | fs::path(__FILE__).parent_path().parent_path() / "test" / "page_addrs.bin"); 42 | MinimalPerfectHash mph; 43 | mph.build(keys); 44 | check_mph(keys, mph); 45 | } 46 | 47 | TEST_CASE("check_rand_u64_dup_idx_29751", TS) { 48 | const auto keys = read_numbers_from_file( 49 | fs::path(__FILE__).parent_path().parent_path() / "test" / "rand_u64_dup_idx_29751.bin"); 50 | MinimalPerfectHash mph; 51 | mph.build(keys); 52 | check_mph(keys, mph); 53 | } 54 | -------------------------------------------------------------------------------- /unit-tests/RankSelect.cpp: -------------------------------------------------------------------------------- 1 | #include "xnu-trace/xnu-trace.h" 2 | 3 | #include 4 | #include 5 | 6 | #define TS "[RankSelect]" 7 | 8 | TEST_CASE("build", TS) { 9 | const auto seq = get_random_sorted_unique_scalars(16, 16); 10 | fmt::print("seq: {:d}\n", fmt::join(seq, ", ")); 11 | } 12 | -------------------------------------------------------------------------------- /unit-tests/memmem-chunking.cpp: -------------------------------------------------------------------------------- 1 | #include "xnu-trace/xnu-trace.h" 2 | 3 | #include 4 | #include 5 | 6 | #define TS "[memmem-chunking]" 7 | 8 | TEST_CASE("smol-chunk", TS) { 9 | const uint8_t haystack[] = {1, 1, 1, 1}; 10 | const uint8_t needle[] = {1}; 11 | const auto one_ptrs = 12 | chunk_into_bins_by_needle(4, haystack, sizeof(haystack), needle, sizeof(needle)); 13 | REQUIRE(one_ptrs.size() == 4); 14 | for (int i = 0; i < 4; ++i) { 15 | REQUIRE(one_ptrs[i] == (void *)&haystack[i]); 16 | } 17 | } 18 | 19 | TEST_CASE("smol2-chunk", TS) { 20 | const uint8_t haystack[] = {1, 0, 1, 0, 1, 0, 1, 0}; 21 | const uint8_t needle[] = {1}; 22 | const auto one_ptrs = 23 | chunk_into_bins_by_needle(4, haystack, sizeof(haystack), needle, sizeof(needle)); 24 | REQUIRE(one_ptrs.size() == 4); 25 | for (int i = 0; i < 4; ++i) { 26 | REQUIRE(one_ptrs[i] == (void *)&haystack[i * 2]); 27 | } 28 | } 29 | 30 | TEST_CASE("smol2-uneven-chunk", TS) { 31 | const uint8_t haystack[] = {1, 0, 1, 0, 1, 1, 0, 0}; 32 | const uint8_t needle[] = {1}; 33 | const auto one_ptrs = 34 | chunk_into_bins_by_needle(4, haystack, sizeof(haystack), needle, sizeof(needle)); 35 | REQUIRE(one_ptrs.size() == 4); 36 | for (int i = 0; i < 4; ++i) { 37 | if (i != 3) { 38 | REQUIRE(one_ptrs[i] == (void *)&haystack[i * 2]); 39 | } else { 40 | REQUIRE(one_ptrs[i] == nullptr); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /unit-tests/xnu-trace-unit-tests-catch2.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jevinskie/xnu-trace/f6f0d03440fc381246a13ccaae75f67d6ffa0fac/unit-tests/xnu-trace-unit-tests-catch2.cpp --------------------------------------------------------------------------------