DTrace on Windows

repository·windows·Indexed 19 days ago

https://github.com/microsoft/dtrace-on-windows

A dynamic tracing framework for real-time system analysis in user and kernel modes on Windows, ported from the OpenDTrace project. This documentation includes detailed guides for ucpp, a lightweight ISO C99-compliant C preprocessor used within the project, covering its operation as a standalone binary or an embeddable lexer library, configuration via tune.h, and integration steps.

Tokens
6.5K
Snippets
13
Records
25
Agent score
68%

What's inside DTrace on Windows

  1. Overview of ucpp

    windows

    ucpp is a lightweight, fast C preprocessor that is fully compliant with the ISO C99 standard (ISO Standard 9899:1999). It is designed to be used either as a standalone command-line program or as an embeddable library/integrated lexer for other projects.

    ucpp operates in two distinct modes:

    1. Lexer Mode: When linked to other code, ucpp acts as an integrated lexer. It outputs a stream of tokens one at a time via calls to the lex() function.
    2. Non-Lexer Mode: ucpp preprocesses text and outputs the resulting text to a file descriptor. If linked to other code, the cpp() function must be called repeatedly; otherwise, it functions as a standalone binary.
  2. Handle pragmas and _Pragma() in ucpp

    windows

    ucpp handles #pragma and _Pragma() with specific behaviors:

    Pragma Dumping

    If the compile-time option PRAGMA_DUMP is set, ucpp attempts to output source text that preserves the token stream, including #pragma directives. Non-void _Pragma() calls are converted to the corresponding #pragma and dumped.

    Macro-replaced Pragmas

    ucpp does not macro-replace the contents of #pragma or _Pragma(). If you need a macro to expand into a pragma, use the following pattern:

    #define pragma_(x) _Pragma(#x)
    #define pragma(x)  pragma_(x)

    _Pragma Evaluation in Directives

    By default, _Pragma is evaluated inside directives like #if, #include, #include_next, and #line. To disable this behavior, define NO_PRAGMA_IN_DIRECTIVE in tune.h.

    #define pragma_(x) _Pragma(#x)
    #define pragma(x)  pragma_(x)
  3. Manage lexer state memory

    windows

    To prevent memory leaks and manage the lifecycle of the preprocessor:

    • To perform a new preprocessing task: Call free_lexer_state() to release buffers used by lexer_state. You must then reset your macro and assertion tables to their initial contents.
    • To release almost all memory: Call wipeout(). This releases nearly all dynamically allocated memory blocks. After wipeout(), the state is equivalent to having just called init_cpp() (which only initializes static tables).
  4. Compile OpenDTrace user-mode components for Windows

    windows

    To build the user-mode components of OpenDTrace on Windows, you must satisfy specific environment requirements and follow a sequence of setup and build steps using PowerShell, Git, and Visual Studio.

    # 1. Clone the repository
    git clone https://github.com/microsoft/DTrace-on-Windows
    
    # 2. Fetch external tools
    .\releng\Get-ExternalTools.ps1
    
    # 3. Build via Visual Studio
    # Open opendtrace.sln and build the solution
  5. Integrate ucpp as an embedded lexer

    windows

    To use ucpp as an integrated lexer (reading files, preprocessing, and outputting a stream of C tokens), compile the code with STAND_ALONE undefined.

    Follow these initialization steps:

    1. Initialize Lexer: Call init_cpp() to initialize the lexer automaton.
    2. Configure Globals: Set the following global variables:
      • no_special_macros: Set to non-zero to prevent defining special macros like __FILE__.
      • c99_compliant: Set to non-zero to define __STDC_VERSION__ as 199901L (default). Set to 0 to omit it.
      • c99_hosted: Set to 1 for hosted environment (default), 0 for non-hosted, or a negative value to not define __STDC_HOSTED__.
      • emit_defines and emit_assertions: Set to 0 during the initial setup step.
    3. Initialize Tables: Call init_tables(). If you provide a non-zero argument, it will also initialize assertions.
    4. Set Include Path: Call init_include_path() to reset the include path to your specified list of paths.
    // Conceptual integration steps based on README
    init_cpp();
    
    no_special_macros = 0;
    c99_compliant = 1;
    c99_hosted = 1;
    emit_defines = 0;
    emit_assertions = 0;
    
    init_tables(0); // 0 to not initialize assertions
    init_include_path(my_paths, path_count);
  6. Initialize and use the ucpp preprocessor

    windows

    To use ucpp as a preprocessor, follow these steps:

    1. Set Global Variables: Configure the following globals:
      • emit_dependencies: Set to 1 to emit dependencies during preprocessing, or 2 to include system include files.
      • emit_defines: Set to non-zero to emit #define macro definitions.
      • emit_assertions: Set to non-zero to emit assertions.
      • emit_output: The FILE * where emitted items are sent.
      • transient_characters: Used for cross-compilation.
    2. Set Initial Filename: Call set_init_filename(filename, is_real), where is_real is true if fopen() can work on the filename.
    3. Initialize Lexer State:
      • Call init_lexer_state().
      • If outputting tokens, call init_lexer_mode(). Otherwise, set ls->flags to DEFAULT_CPP_FLAGS and ls->output to the target FILE *.
    4. Configure Input: Set ls->input to the FILE * containing the source.
    5. Configure Environment: Use add_incpath() for include paths, define_macro()/undef_macro() for macros, and make_assertion()/destroy_assertion() for assertions.
    6. Enter Files: Call enter_file() (required if not in lexer mode or if LINE_NUM is set).
    7. Execute:
      • Lexer Mode: Call lex() repeatedly. A non-zero return is an error. CPPERR_EOF indicates end-of-input.
      • Preprocessor Mode: Call cpp() repeatedly. A positive return is an error. CPPERR_EOF indicates end-of-file.
    8. Finalize: Call check_cpp_errors() after end-of-file. In non-lexer mode, call flush_output().
  7. Install and compile ucpp

    windows

    To install ucpp as a standalone binary, follow these steps:

    1. Extract: Uncompress the archive and extract the source files.
    2. Configure tune.h: Edit tune.h to set compile-time options (e.g., LOW_MEM, UCPP_MMAP, PRAGMA_TOKENIZE) to suit your target architecture and performance needs.
    3. Configure Makefile: Edit the Makefile to define CC and FLAGS variables.
    4. Compile: Run the make command.
    5. Manual Compilation: If make is unavailable, compile each file separately and link them. Crucial: You must define the macro STAND_ALONE when compiling cpp.c to create a standalone binary.

    Note: You require an ISO-C90 (ANSI) C compiler suite with a standard library to compile ucpp.

    # Standard build process
    make
    
    # If compiling manually without make, ensure STAND_ALONE is defined for cpp.c
    # Example (conceptual):
    gcc -DSTAND_ALONE -c cpp.c
  8. Install DTrace on Windows

    windows

    To install DTrace on Windows, ensure you are using Windows 10 x64 Build 1903 or higher. Note that DTrace is only available for 64-bit platforms and only captures traces for 64-bit processes.

    Follow these steps:

    1. Enable dtrace in the Boot Configuration Data (BCD) store using bcdedit /set dtrace on. You must repeat this step whenever you install a newer build of Windows.
    2. Download and execute the DTrace installer.
    3. Configure the _NT_SYMBOL_PATH environment variable to enable local symbol caching.
    4. Reboot the target machine.
    bcdedit /set dtrace on
  9. Requirements for compiling OpenDTrace on Windows

    windows

    Before attempting to build the project, ensure the following dependencies are installed:

    • Windows WDK and SDK: Version 1903 or later.
    • Git for Windows: Required for cloning the repository.
    • Windows OS: Windows 10 Anniversary Update or newer.
  10. Configure ucpp for cross-compilation

    windows

    When using a pre-C99 compiler or targeting a different machine architecture, you may need to adjust how ucpp evaluates #if expressions.

    Character Constants

    To ensure character constants are interpreted correctly in the source character set context, you can provide a conversion array.

    • As a library component: Define a global variable transient_characters pointing to an array of 256 int values. transient_characters[x] should contain the value of the character whose value is x in the source character set.
    • As a standalone tool: Hard-code the conversion table into eval.c and make transient_characters[] point to it statically.

    Wide Character Signedness

    By default, wide characters match the signedness of char on the build host. To override this, define:

    • WCHAR_SIGNEDNESS 0 for unsigned wide characters.
    • WCHAR_SIGNEDNESS 1 for signed wide characters.

    Arithmetic Evaluation

    ucpp supports two evaluation modes:

    1. Native integer types: Uses one signed and one unsigned native type.
    2. Emulated big integers: Represents numbers using two values of an unsigned type, supporting widths from 2 bits up to twice the native unsigned type size. This mode handles signed values using two's complement.

    To enable error/warning reporting for implementation-defined or undefined arithmetic behavior (like overflows or division by zero), define ARITHMETIC_CHECKS in tune.h.

  11. Configure character set limits via MAX_CHAR_VAL

    windows

    The ucpp lexer assumes that source characters (those with syntactic value, excluding comments and string literal contents) have a strictly positive value lower than MAX_CHAR_VAL.

    While C standard characters are strictly positive, you may need to adjust the MAX_CHAR_VAL constant to accommodate your specific source character set.

  12. How to select probes using dtrace flags

    windows

    DTrace allows you to filter and target specific probes using several flags. This is useful for narrowing down the scope of tracing to specific modules, functions, or providers.

    • By Provider: Use -P <provider> to enable or list probes matching a specific provider name.
    • By Module: Use -m <[provider:]module> to enable or list probes matching a specific module name.
    • By Function: Use -f <[[provider:]module:]func> to enable or list probes matching a specific function name.
    • By Probe Name: Use -n <[[[provider:]module:]func:]name> to enable or list probes matching a specific probe name.
    • By Probe ID: Use -i <probe-id> to enable or list probes matching a specific probe ID.