diff --git a/.editorconfig b/.editorconfig index 60e03a5d..dff1dba7 100644 --- a/.editorconfig +++ b/.editorconfig @@ -6,19 +6,6 @@ trim_trailing_whitespace = true insert_final_newline = true end_of_line = crlf charset = latin1 - -[*.cpp] -indent_style = space -indent_size = 4 - -[*.h] -indent_style = space -indent_size = 4 - -[*.rc] -indent_style = space -indent_size = 4 - -[*.yml] indent_style = space indent_size = 4 +charset = utf-8 \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..6be5fc01 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,13 @@ +* text=auto eol=crlf +*.sh text=auto eol=lf + +# Define binary files +*.sys -text diff +*.sln -text diff +*.suo -text diff +*.vcxproj -text diff +*.gif -text diff +*.ico -text diff +*.jpeg -text diff +*.jpg -text diff +*.png -text diff diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 7cd831a1..477bfcb6 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -14,7 +14,7 @@ jobs: build: # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. # You can convert this to a matrix build if you need cross-platform coverage. - # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-matrix runs-on: windows-2022 steps: diff --git a/.gitignore b/.gitignore index 2621b8fc..f7b5a035 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .vs/ +.vscode/ vs2022/ build/ out/ @@ -7,4 +8,4 @@ packages/ bin obj/ Testing/ -install/ \ No newline at end of file +install/ diff --git a/CMakeLists.txt b/CMakeLists.txt index f6c51e04..78528853 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,6 +2,7 @@ cmake_minimum_required(VERSION 3.16) set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_EXTENSIONS OFF) +set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake") include(CMakeLists.buildnumber) @@ -19,6 +20,8 @@ project( enable_testing() +add_compile_options(/MP) + if(BUILD_WITH_ASAN) add_compile_options(/fsanitize=address /D_DISABLE_VECTOR_ANNOTATION=0) message("-- build with ASAN enabled") @@ -38,27 +41,27 @@ add_library(msvc_console_link_options INTERFACE) target_compile_features(project_compile_features INTERFACE cxx_constexpr cxx_auto_type cxx_final cxx_std_20) target_compile_definitions(project_definitions - INTERFACE - UNICODE - NOMINMAX - WIN32_LEAN_AND_MEAN - _CRT_SECURE_NO_WARNINGS - _WINSOCK_DEPRECATED_NO_WARNINGS - WIN32 - WIN64 - _SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING - "_WIN32_WINNT=0x0601" -) -# "_ITERATOR_DEBUG_LEVEL=0" # trips over debug/release mismatches + INTERFACE + UNICODE + NOMINMAX + WIN32_LEAN_AND_MEAN + _CRT_SECURE_NO_WARNINGS + _WINSOCK_DEPRECATED_NO_WARNINGS + WIN32 + WIN64 + _SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING + "_WIN32_WINNT=0x0601" +) +# "_ITERATOR_DEBUG_LEVEL=0" # trips over debug/release mismatches # /Oy and /RELEASE are added to avoid tripping malware detection # this is quite random, ymmv target_compile_options(project_compile_options INTERFACE /utf-8 /Oy) target_link_options(msvc_gui_link_options INTERFACE /SUBSYSTEM:WINDOWS /NOLOGO /RELEASE) target_link_options(msvc_console_link_options INTERFACE /SUBSYSTEM:CONSOLE /NOLOGO /RELEASE) - -# warnings (the project picked up a bunch of unresolved warnings with migrating to new VS versions and when {fmt} was added, work in progress...) -target_compile_options(project_compile_options INTERFACE /W4 /w14242 /w14254 /w14263 /w14265 /w14287 /w14289 /w14296 /w14311 /w14545 /w14546 /w14547 /w14549 /w14555 /w14640 /w14826 /w14905 /w14906 /w14928) + +# warnings (the project picked up a bunch of unresolved warnings with migrating to new VS versions and when {fmt} was added, work in progress...) +target_compile_options(project_compile_options INTERFACE /W4 /w14242 /w14254 /w14263 /w14265 /w14287 /w14289 /w14296 /w14311 /w14545 /w14546 /w14547 /w14549 /w14555 /w14640 /w14826 /w14905 /w14906 /w14928 /we4715) add_library(project::definitions ALIAS project_definitions) add_library(project::compile_features ALIAS project_compile_features) @@ -77,4 +80,3 @@ message("CMAKE_C_COMPILER: ${CMAKE_C_COMPILER}") message("CMAKE_CXX_COMPILER: ${CMAKE_CXX_COMPILER}") message("will be installed in: ${CMAKE_INSTALL_PREFIX}") message("\n") - diff --git a/README.md b/README.md index dedd4212..52dac460 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,31 @@ --- Notice: +[![Discord Invite](https://img.shields.io/badge/Discord%20Live%20Coding%20Chat-invite-blue)](https://discord.gg/V6HRYgBFUX) +![Mastodon Follow](https://img.shields.io/mastodon/follow/109524813797978857?domain=https%3A%2F%2Ftoot.community&style=social) +![Github Follow](https://img.shields.io/github/followers/janwilmans) -* I'm looking for maintainers, so if you would like to join me in bugfixing or adding features, please let me know. -* 2022 update: The project was migrated to VS2022 and boost 1.80, WindowsXP (32 bit) support was removed. -* the OutputForwarder companion extension for VS2022 can be found in the [Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=JanWilmans.OutputForwarder2022) -* nuget.exe can be downloaded from https://www.nuget.org/downloads +## Debugview++ v1.20.0.0 (released 23 April 2026) -Debugview++, currently at v1.9 (2024 Update!) ------------------------------- - - +- bugfix: every 5000th line was not visible in the viewer (thanks to roman-orekhov for reporting and reproducing) +- bugfix: unicode message were messed up (depending on windows utf-8 settings) +- new features: capture kernel messages, verbose mode and pass-through mode, big thanks to x64bugreport and 593749519 for providing information, contributions and test scenarios. +- fixed #365: support for messages from UWP applications (poorly tested), feedback welcome! +- fixed #396: depending on the runtime dlls, this broke when we moved to cmake, now we link the runtime statically again. +- fixed title bar not being updated when 'Global Win32' was toggled. +- new commandline argument /tab: to select a tab that get focus at startup, contributed by KirisameMarisa +- removed color support from 'include' filter by request (a highlight filter can still achieve the same result), contributed by Ishmaeel. +- faster building by adding /MP. -![Mastodon Follow](https://img.shields.io/mastodon/follow/109524813797978857?domain=https%3A%2F%2Ftoot.community&style=social) +-- Notice: + +- This project is in low-maintenance mode, this means new features are only added if PRs are provided with tests. +- Updates are limited to keeping the status-quo and OS-related updated. +- I'm looking for maintainers, so if you would like to join me in bugfixing or adding features, please let me know. +- 2022 update: The project was migrated to VS2022 and boost 1.80, WindowsXP (32 bit) support was removed. +- the OutputForwarder companion extension for VS2022 can be found in the [Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=JanWilmans.OutputForwarder2022) +- nuget.exe can be downloaded from https://www.nuget.org/downloads -[![All Releases](https://img.shields.io/github/downloads/CobaltFusion/DebugViewPP/total.svg)](https://github.com/CobaltFusion/DebugViewPP/releases/latest) +## Debugview++, currently at v1.9 (2024) + +[![All Releases](https://img.shields.io/github/downloads/CobaltFusion/DebugViewPP/total.svg)](https://github.com/CobaltFusion/DebugViewPP/releases/latest) [![Current Release](https://img.shields.io/github/downloads/CobaltFusion/DebugViewPP/latest/total.svg)](https://github.com/CobaltFusion/DebugViewPP/releases/latest) [![Appveyor Build status](https://ci.appveyor.com/api/projects/status/ho962elp8chrdga0/branch/develop?svg=true)](https://ci.appveyor.com/project/janwilmans/debugview/branch/develop) @@ -21,13 +33,14 @@ alt="follow on Twitter"> [Download latest release](https://github.com/djeedjay/DebugViewPP/releases), I would really like to hear what you think! Leave any [**comments here**](https://github.com/djeedjay/DebugViewPP/issues/283) -[Download head version ](https://ci.appveyor.com/project/janwilmans/debugview/build/artifacts) ** only pick this if your feeling lucky and want to try the latest commits ** +[Download head version](https://ci.appveyor.com/project/janwilmans/debugview/build/artifacts) ** only pick this if your feeling lucky and want to try the latest commits ** -Questions? Tweet me at [**@janwilmans**](https://twitter.com/janwilmans) or chat on skype at 'janwilmans'. +Questions? Find me at [@janwilmans.bsky.social](janwilmans.bsky.social) or chat on discord as 'janwilmans'. There is also a [slack channel](https://cpplang.slack.com/messages/debugviewpp). If you need an invite, go [here](https://cpplang.now.sh/). Debugview++ started as a viewer for Win32 OutputDebugString messages in the style of Sysinternals DebugView. However, it can now be attached to virtually any other kind of logging, such as: + - tailing ascii and UTF logfiles (just drag it onto the window) - Android ADB (or any console based standard output) - serial ports (using plink) @@ -36,8 +49,7 @@ Sysinternals DebugView. However, it can now be attached to virtually any other k See examples down below. -Sponsors --------- +## Sponsors This project is sponsored by: @@ -45,7 +57,7 @@ This project is sponsored by: Build in code analysis, handy auto-fixes and refactoring options -[![Backtrace logo](art/backtrace_logo.png)](https://backtrace.io/ ) +[![Backtrace logo](art/backtrace_logo.png)](https://backtrace.io/) Gather and analyse crash information. @@ -53,14 +65,14 @@ Gather and analyse crash information. We use Incredibuild to make use of all cores of multiple machines to accelerate building our C++ projects. -So when is this Debugview++ thing useful? ------------------------------------------- +## So when is this Debugview++ thing useful? - first of all, with debugview++ you can see messages from different processes, not just 'attached' processes. - also: filtering, coloring and linking. To make sense of a large amount of information humans need to filter it or order it understand it. Also it helps if important events have different colors to quickly interpret the occurring patterns. -- finally, filtering is nice, but sometimes you need to see a line in its context to understand it, this is where linked views can help to quickly switch between a fully filtered view and a fully detailed view. +- finally, filtering is nice, but sometimes you need to see a line in its context to understand it, this is where linked views can help to quickly switch between a fully filtered view and a fully detailed view. New in stable version 1.8.x: + - bugfixes (namely in file-tailing) - tested on windows 10 - last version with (official) XP support (v141_xp target) @@ -70,23 +82,25 @@ New in stable version 1.8.x: - no other new features planned, if you're missing something you need, file an issue! Features we dream about and will create when we choose to spend the time: + - a gantt chart-like view, a horizontal timeline, with bars/flags/signs on it to identify events - a better plugin based input system - transparent background streaming to disk - proper memory limits Known issues: + - the history limit doesn't work right, this is troublesome for long-running duration-tests. A workaround is to send 'DBGVIEWCLEAR' before each test-cycle (this clears all logs from memory). - there is no 'pass-through' mode like the original dbgview had, if you can help me implement this, please contact me. - same goes for catching kernel messages, help wanted. -References ----------- -[OutputDebugString on MSDN](https://msdn.microsoft.com/en-us/library/windows/desktop/aa363362(v=vs.85).aspx) +## References + +[OutputDebugString on MSDN]() + +## Screenshot -Screenshot ------------ ![DebugView++ Screenshot](art/syntax_high.png "DebugView++ Screenshot") Here are some features: @@ -99,12 +113,12 @@ Here are some features: - tailing files (drag ascii or UTF files into debugview to tail it) And more features: - + - capture both Win32 and Global Win32 messages - tabbed views - resolve process PID to name and track their lifetime - filter by process or message -- advanced filtering, exclude, track, stop, clear (optionally using regular expressions) +- advanced filtering, exclude, track, stop, clear (optionally using regular expressions) - line and token highlighting (create your own syntax highlighting) - SAIT (search-as-I-type) + token highlighting - bookmarks @@ -113,7 +127,7 @@ And more features: - commandline version - capture stdin piped messages, allows you to connect any kind of logging - beep-filter for monitoring without seeing the screen (To hear it make sure a 'Default Beep' sound is defined in Control Panel->Sounds) -- clear Log now releases the message buffer instead of reusing the memory (useful when running debugview +- clear Log now releases the message buffer instead of reusing the memory (useful when running debugview for a very long time) - tailing logfiles over samba network (experimental) - support for reading and tailing Sysinternals Dbgview logfiles (in the four most common formats) @@ -124,18 +138,18 @@ And more features: - View->Process Colors, easy way to give every process its own color! - Options->Link views, best effort to synchronize the line-selection over all views -Download old version (stable, dated 20 Sept 2015) ------------------------ -+ [DebugView v1.5.x Zipped executables](https://github.com/CobaltFusion/DebugViewPP/files/1408599/DebugView.v1.5.zip) -+ [DebugView v1.5.x Win32 installer](https://github.com/CobaltFusion/DebugViewPP/files/1408607/DebugView.v1.5_msi.zip) +## Download old version (stable, dated 20 Sept 2015) + +- [DebugView v1.5.x Zipped executables](https://github.com/CobaltFusion/DebugViewPP/files/1408599/DebugView.v1.5.zip) +- [DebugView v1.5.x Win32 installer](https://github.com/CobaltFusion/DebugViewPP/files/1408607/DebugView.v1.5_msi.zip) Screenshot demonstrating bookmarks and highlighting features. **Highlighted**: -- regex (token filter): ``[^\s]*\\[\\\w+\.\s]+`` filenames in blue -- regex (token filter): ``0x\w+`` hexadecimal numbers in red -- regex (highlight filter): ``Unittest`` lines with the word 'Unittest' have a lightgreen background +- regex (token filter): `[^\s]*\\[\\\w+\.\s]+` filenames in blue +- regex (token filter): `0x\w+` hexadecimal numbers in red +- regex (highlight filter): `Unittest` lines with the word 'Unittest' have a lightgreen background - a doubleclick on 'bytes' causes all instances of 'bytes' to highlight in yellow See http://www.cplusplus.com/reference/regex/ECMAScript/ for all options for supported regular expressions @@ -146,8 +160,7 @@ See http://www.cplusplus.com/reference/regex/ECMAScript/ for all options for sup Screenshot demonstrating connecting to ADB logcat (Android Debug Bridge) -More examples -------------- +## More examples **Connect any pipe**: @@ -155,7 +168,7 @@ To connect directly to a port or service, [plink] can be used, make sure an inst > plink -ssh -batch -v 192.168.0.1 2>&1 | debugview++ -Notice that 2>&1 is used *before* the pipe (|) symbol to redirect stderr to stdout. +Notice that 2>&1 is used _before_ the pipe (|) symbol to redirect stderr to stdout. [plink]: http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html @@ -166,20 +179,18 @@ And connect DebugView++ using Log->Connect DebugView Agent. Note that 'Log->Conn **Use RegexGroups + Token Highlighting**: -Suppose you want to highlight some data value in your logging, since the actually value may differ, you cannot use normal matching to highlight them. With RegexGroups you can match text _before_ or _after the actual token you want to highlight. +Suppose you want to highlight some data value in your logging, since the actually value may differ, you cannot use normal matching to highlight them. With RegexGroups you can match text _before_ or \_after the actual token you want to highlight. Example: ![FilterDialog Screenshot](art/regexgroups.png "RegexGroups Screenshot") -Filters: --------- +## Filters: ![FilterDialog Screenshot](art/filterdialog.png "FilterDialog Screenshot") - Filters can be defined per view, for example choose File -> New View, and the filter dialog will popup. -Pressing OK will open a new view without any filters. +Pressing OK will open a new view without any filters. Different types of filters: @@ -188,25 +199,25 @@ just type any word or part of a word to match. - include: if an include filter is added only lines containing a matching expression will be included. - exclude: lines containing a matching expression will be excluded from the view, excluding always takes precedence over including -- once: only the first line containing a matching expression will be included, this resets automatically at 'clear view'. +- once: only the first line containing a matching expression will be included, this resets automatically at 'clear view'. - highlight: lines containing a matching expression will be highlighted using the specified foreground and background colors - token: only the matching expression will be highlighted using the specified foreground and background colors -- track: lines containing a matching expression will be focused and centered if possible. Note: auto scroll turns off if a track filter is matched +- track: lines containing a matching expression will be focused and centered if possible. Note: auto scroll turns off if a track filter is matched - stop: if a matching expression is found autoscroll is turned off, all track filters will be disabled and the line is focused. Note: stop filters work only if autoscroll is on, think of a stop-filter as a single-shot track filter -- beep: a standard windows beep (configurable in config panel->sounds) is played +- beep: a standard windows beep (configurable in config panel->sounds) is played -*Practical uses*: +_Practical uses_: Include, exclude, once and highlight filters are the most intuitive filters to use. Track and stop can be a little confusing, let me try to give some examples. -**track**: use this filter to focus interesting lines that do not occur very often, but at a regular interval, for example, so you are monitoring a process that logs output every 30 seconds and you need to check the result. +**track**: use this filter to focus interesting lines that do not occur very often, but at a regular interval, for example, so you are monitoring a process that logs output every 30 seconds and you need to check the result. **stop**: this filter is good when some special event occurs (an exception?) and you want to inspect the context of the event in the log before continuing. A press of the 'end' button will resume auto scrolling. -Other features: ----------------- +## Other features: **views**: + - reset view; make the first line in the logbuffer the first line in the view - reset view to this line; make the current like the first line in the view - clear view; make next incoming message the first line in the view @@ -223,8 +234,7 @@ Now you can monitor the filtered view, and when your event occurs, select a line **process colors**: If enabled each process (even processed with identical names) will get a its own background color automatically without adding any filters. -Other documentation: --------------------- +## Other documentation: **Auto Scroll**: scrolls down to new incoming lines automatically @@ -235,11 +245,12 @@ turned off if any other line is selected. **ClockTime**: when enabled the time is displayed as provided by the system's real-time clock (RTC). Such a timestamp has a 16ms resolution on a typical desktop PC. When disabled, time is displayed as a relative time to the first message, however this timestamp is obtained from the High-Performance Counter (HPC) which typically has a sub-microsecond resolution. -The resolution should not be confused with accuracy here, the recorded timestamp is not the actual time the message occurred, it is the time the message was received by DebugView++. Also there is no guarantee that the time between occurrence and reception of messages is constant, *however* in practice this is **pretty** constant :) +The resolution should not be confused with accuracy here, the recorded timestamp is not the actual time the message occurred, it is the time the message was received by DebugView++. Also there is no guarantee that the time between occurrence and reception of messages is constant, _however_ in practice this is **pretty** constant :) + +## How to build -How to build ------------- This project has a special configuration: + - 32bit debug/release configurations are targeted to v140_XP, SDK v7.0 (windows XP SP3 and up) - 64bit debug/release configurations are targeted to v141, SDK v8.1 (runs on windows 7 and up) - only the release configurations also build the Wix installer @@ -261,9 +272,9 @@ Since we use ATL and support Windows XP still some extra installation options ar ![VS2017 Option screen](art/vs2017_options.png "VS2017 Option screen") - zip.exe, http://gnuwin32.sourceforge.net/packages/zip.htm, choose [zip-3.0-setup.exe] - decompress the archive and you're done (add zip.exe to the path) + decompress the archive and you're done (add zip.exe to the path) -# VS2019 +# VS2019 There are [known issues](https://twitter.com/janwilmans/status/1369956897726599176?s=20) with compilation using Visual Studio 2019 16.10.x (preview at March 11, 2021). Know good combinations are Boost 1.69 + Visual studio 2019 16.9.1. (I have seen problems with Asio in Boost 1.72 + Visual studio 2019 16.9.1 and Boost 1.72 + Visual studio 2019 16.10.x) @@ -275,4 +286,3 @@ mailto:janwilmans _at_ gmail.com Gert-Jan de Vos mailto:boosttestui@on.nl - diff --git a/application/CobaltFusion/CMakeLists.txt b/application/CobaltFusion/CMakeLists.txt index 44ea63b3..9d7ec27d 100644 --- a/application/CobaltFusion/CMakeLists.txt +++ b/application/CobaltFusion/CMakeLists.txt @@ -13,12 +13,12 @@ add_library(${PROJECT_NAME} add_library(dv::cobaltfusion ALIAS ${PROJECT_NAME}) target_link_libraries(${PROJECT_NAME} - PRIVATE - project::definitions - project::compile_features - project::compile_options - nuget::boost - dv::win32 +PRIVATE + project::definitions + project::compile_features + project::compile_options + nuget::boost + dv::win32 ) target_include_directories(${PROJECT_NAME} PUBLIC diff --git a/application/CobaltFusion/Timer.cpp b/application/CobaltFusion/Timer.cpp index 8f03760d..d0671ab4 100644 --- a/application/CobaltFusion/Timer.cpp +++ b/application/CobaltFusion/Timer.cpp @@ -11,6 +11,13 @@ namespace fusion { +long long GetTicks() +{ + LARGE_INTEGER li; + QueryPerformanceCounter(&li); + return li.QuadPart; +} + Timer::Timer() : m_timerUnit(0.0), m_init(false), @@ -33,21 +40,22 @@ void Timer::Reset() double Timer::Get() { - auto ticks = GetTicks(); - std::lock_guard lock(m_mutex); + return GetTimeSinceOrigin(GetTicks()); +} + +double Timer::GetTimeSinceOrigin(long long ticks) +{ + // double-checked locking, prevents pessimizing the happy-path if (!m_init) { - m_offset = ticks; - m_init = true; + std::lock_guard lock(m_mutex); + if (!m_init) + { + m_offset = ticks; + m_init = true; + } } return (ticks - m_offset) * m_timerUnit; } -long long Timer::GetTicks() -{ - LARGE_INTEGER li; - QueryPerformanceCounter(&li); - return li.QuadPart; -} - } // namespace fusion diff --git a/application/CobaltFusionTest/CMakeLists.txt b/application/CobaltFusionTest/CMakeLists.txt index 2672727e..00266e2e 100644 --- a/application/CobaltFusionTest/CMakeLists.txt +++ b/application/CobaltFusionTest/CMakeLists.txt @@ -3,12 +3,12 @@ project(CobaltFusionTest) add_executable(${PROJECT_NAME} CobaltFusionTest.cpp TestExecutor.cpp TestGuiExecutor.cpp) target_link_libraries(${PROJECT_NAME} - PRIVATE - project::definitions - project::compile_features - project::compile_options - nuget::boost_test - CobaltFusion +PRIVATE + project::definitions + project::compile_features + project::compile_options + nuget::boost_test + CobaltFusion ) target_include_directories(${PROJECT_NAME} PUBLIC diff --git a/application/DbgMsgSrc/CMakeLists.txt b/application/DbgMsgSrc/CMakeLists.txt index 43f08e33..b1b4355f 100644 --- a/application/DbgMsgSrc/CMakeLists.txt +++ b/application/DbgMsgSrc/CMakeLists.txt @@ -3,15 +3,15 @@ project(DbgMsgSrc) add_executable(${PROJECT_NAME} DbgMsgSrc.cpp Timer.cpp) target_link_libraries(${PROJECT_NAME} - PRIVATE - project::definitions - project::compile_features - project::compile_options - project::console_link_options - nuget::boost - dv::cobaltfusion - dv::library - dv::win32 +PRIVATE + project::definitions + project::compile_features + project::compile_options + project::console_link_options + nuget::boost + dv::cobaltfusion + dv::library + dv::win32 ) target_include_directories(${PROJECT_NAME} PUBLIC diff --git a/application/DbgMsgSrc/DbgMsgSrc.cpp b/application/DbgMsgSrc/DbgMsgSrc.cpp index 0c7b9a5a..a496b9a9 100644 --- a/application/DbgMsgSrc/DbgMsgSrc.cpp +++ b/application/DbgMsgSrc/DbgMsgSrc.cpp @@ -1,553 +1,574 @@ -// (C) Copyright Gert-Jan de Vos and Jan Wilmans 2013. -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE_1_0.txt or copy at -// http://www.boost.org/LICENSE_1_0.txt) - -#include "dbgstream.h" -#include "Win32/Win32Lib.h" -#include "Timer.h" - -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include "shellapi.h" - -#pragma comment(lib, "Ws2_32.lib") -#pragma comment(lib, "psapi.lib") - -namespace fusion { - -namespace Win32 { - -class WinsockInitialization : noncopyable -{ -public: - explicit WinsockInitialization(int major = 2, int minor = 2) - { - WSADATA wsaData = {0}; - int rc = WSAStartup(MAKEWORD(major, minor), &wsaData); - if (rc != 0) - ThrowWin32Error(rc, "WSAStartup"); - } - - ~WinsockInitialization() - { - WSACleanup(); - } -}; - -void WSAThrowLastError(const std::string& what) -{ - Win32::ThrowWin32Error(WSAGetLastError(), what); -} - -} // namespace Win32 - -namespace DbgMsgSrc { - -void DbgMsgSrc(double freq) -{ - unsigned t0 = GetTickCount(); - unsigned count = 0; - double msgPerTick = freq * 1e-3; - for (;;) - { - Sleep(10); - while (count < (GetTickCount() - t0) * msgPerTick) - { - if (count % 10 == 0) - cdbg << "Een, twee, drie, vier, vijf, zes, zeven, acht, negen, tien, elf, twaalf, dertien, veertien, vijftien, zestien, zeventien, achttien, negentien, twintig, eenentwintig, tweeentwintig, drieentwintig, vierentwintig, vijfentwintig, zesentwintig, zevenentwintig, achtentwintig, negenentwintig, dertig.\n"; - else - cdbg << "Message #" << count << "\n"; - ++count; - } - } -} - -void DbgMsgTest() -{ - OutputDebugStringA("Message 1 without newline"); - OutputDebugStringA("Message 2 without newline"); - OutputDebugStringA("Message 3 with newline\n"); - OutputDebugStringA("Message with\nembedded newline\n"); - OutputDebugStringA("This should look like a table in a non-proportionally spaced font like 'Courier'"); - OutputDebugStringA("Columnn1\tColumnn2\tColumnn3\tColumnn4\tColumnn5"); - OutputDebugStringA("1\t\t2\t\t3\t\t4\t\t5"); - OutputDebugStringA("A\t\tB\t\tC\t\tD\t\tE"); - OutputDebugStringA("11\t\t12\t\t13\t\t14\t\t15"); - OutputDebugStringA("21\t\t22\t\t23A\t\t24\t\t25"); - - OutputDebugStringA(" This line has 1 space prefixed"); - OutputDebugStringA(" This line has 2 space prefixed"); - OutputDebugStringA(" This line has 3 space prefixed"); - OutputDebugStringA(" This line has 4 space prefixed"); - - OutputDebugStringA("HighLighting test: Double-click the word 'lines' in each of the following lines and make sure that word is highlighted."); - - OutputDebugStringA("\t123\t \t123\t \t123\t \t123\t This lines starts with tab"); - OutputDebugStringA(" \t123\t \t123\t \t123\t \t123\t This lines starts with 1 space + tab"); - OutputDebugStringA(" \t123\t \t123\t \t123\t \t123\t This lines starts with 2 spaces + tab"); - OutputDebugStringA(" \t123\t \t123\t \t123\t \t123\t This lines starts with 3 spaces + tab"); - OutputDebugStringA(" \t123\t \t123\t \t123\t \t123\t This lines starts with 4 spaces + tab"); - OutputDebugStringA("2LongLine: Very Long Message that ends in a single newline. Very Long Message that ends in a single newline. Very Long Message that ends in a single newline. Very Long Message that ends in a single newline. Very Long Message that ends in a single newline. Very Long Message that ends in a single newline. Very Long Message that ends in a single newline. Very Long Message that ends in a single newline. Very Long Message that ends in a single newline. Very Long Message that ends in a single newline. Very Long Message that ends in a single newline. Very Long Message that ends in a single newline. Very Long Message that ends in a single newline. Very Long Message that ends in a single newline. Message ends HERE.\n"); - - OutputDebugStringA("Message without newline (tid: 9000)"); - OutputDebugStringA("Message without newline (tid: 9001)"); - OutputDebugStringA("Message without newline (tid: 9002)"); - OutputDebugStringA("Message without newline (tid: 9003)"); - OutputDebugStringA("Message without newline (tid: 9001)"); - OutputDebugStringA("Message without newline (tid: 9002)"); - OutputDebugStringA("Message without newline (tid: 9003)"); - OutputDebugStringA("Message without newline (tid: 9003)"); - OutputDebugStringA("Message without newline (tid: 9002)"); - OutputDebugStringA("Message without newline (tid: 9001)"); - OutputDebugStringA("Message without newline (tid: 9000)"); - OutputDebugStringA("Message without newline (address: 0x01020304)"); - OutputDebugStringA("Message without newline (address: 0xDEADBEEF)"); - OutputDebugStringA("Message without newline (address: 0xFEEDBEEF)"); - OutputDebugStringA("Message without newline (address: 0xB105F00D)"); - OutputDebugStringA("Message without newline (address: 0x0DEFACED)"); - OutputDebugStringA("Message without newline (address: 0xBAADF00D)"); - OutputDebugStringA("Message without newline (address: 0xCAFEBABE)"); - OutputDebugStringA("Message without newline (address: 0xDEADC0DE)"); - OutputDebugStringA("Message without newline (address: 0xDEADC0DE)"); -} - -void SocketTest() -{ - OutputDebugStringA("1"); - OutputDebugStringA("22"); - OutputDebugStringA("333"); - OutputDebugStringA("4444"); - OutputDebugStringA("55555"); - OutputDebugStringA("666666"); - OutputDebugStringA("1010101010"); -} - -void SendMessageTestNotepad(std::string msg) -{ - HWND notepad = FindWindowA(NULL, "Untitled - Notepad"); - HWND edit = FindWindowExA(notepad, NULL, "EDIT", NULL); - SendMessageA(edit, EM_REPLACESEL, TRUE, (LPARAM)msg.data()); -} - -void SendMessageDebugViewpp(std::string msg) -{ - HWND debugviewpp = FindWindowA(NULL, "[Capture Win32] - DebugView++"); - SendMessageA(debugviewpp, EM_REPLACESEL, GetCurrentProcessId(), (LPARAM)msg.data()); -} - -void DbgMsgClearTest() -{ - char buffer[200]; - - int line = 1; - for (int i = 0; i < 400; ++i) - { - sprintf_s(buffer, "Message %d", line); - OutputDebugStringA(buffer); - ++line; - } - - OutputDebugStringA(" ----> DBGVIEWCLEAR\n"); - - for (int i = 0; i < 10; ++i) - { - sprintf_s(buffer, "Message %d", line); - OutputDebugStringA(buffer); - ++line; - } -} - -int getMilliCount() -{ - timeb tb; - ftime(&tb); - return tb.millitm + (tb.time & 0xfffff) * 1000; -} - -void testLongString() -{ - int length = 15000; - std::ostringstream ss; - ss << "1234567890ABCDEF-%s {0} \t\r\n"; - ss << length; - std::string test = ss.str(); - for (size_t i = 0; i < length - test.size() - 1; ++i) - ss << "X"; - - ss << "\n"; - test = ss.str(); - - OutputDebugStringA(test.c_str()); -} - -std::vector GetFileContent(const std::string& filename) -{ - std::fstream fs; - fs.open(filename, std::fstream::in); - - if (!fs) - { - std::cout << "\"" << filename << "\" not found!\n"; - return {}; - } - - std::cout << "Reading " << filename << "...\n"; - std::vector lines; - std::string line; - while (getline(fs, line)) - lines.push_back(line); - fs.close(); - return lines; -} - -void TestODS(const std::string& filename) -{ - auto lines = GetFileContent(filename); - std::cout << "writing... " << lines.size() << " lines\n"; - Timer timer; - auto t1 = timer.now(); - for (const auto& s : lines) - { - OutputDebugStringA(s.c_str()); - } - auto elepsed = timer.now() - t1; - std::cout << "OutputDebugStringA " << lines.size() << " lines, took: " << static_cast(Timer::ToMs(elepsed)) << " ms\n"; -} - -void TestSendMessage(const std::string& filename) -{ - auto lines = GetFileContent(filename); - std::cout << "writing... " << lines.size() << " lines\n"; - Timer timer; - - HWND debugviewpp = FindWindow(NULL, L"[Capture Win32] - DebugView++"); - - auto t1 = timer.now(); - for (const auto& s : lines) - { - SendMessage(debugviewpp, EM_REPLACESEL, GetCurrentProcessId(), (LPARAM)s.data()); - } - auto elepsed = timer.now() - t1; - std::cout << "SendMessage " << lines.size() << " lines, took: " << static_cast(Timer::ToMs(elepsed)) << " ms\n"; -} - -void EndlessTest() -{ - int length = 40; // 260 - std::ostringstream ss; - ss << "123456:jan-7890_ABC__DEF-te m_test"; - ss << length; - std::string test = ss.str(); - for (size_t i = 0; i < length - test.size() - 1; ++i) - ss << "X"; - - ss << "\n"; - test = ss.str(); - - // testLongString(); - - for (;;) - { - long t1 = getMilliCount(); - - for (int a = 0; a < 5; ++a) - { - OutputDebugStringA(" ## before me ##\n"); - - // write exactly 2MB to the debug buffer; - for (int i = 0; i < 9; ++i) - OutputDebugStringA(test.c_str()); - - long t2 = getMilliCount(); - OutputDebugStringA(" ## tracking me ##\n"); - std::cout << "took: " << t2 - t1 << " ms\n"; - Sleep(50); - } - // OutputDebugStringA(" ----> DBGVIEWCLEAR\n"); - } -} - -void SeparateProcessTest() -{ - std::cerr << "SeparateProcessTest\n"; - for (;;) - { - auto result = ShellExecute(nullptr, L"open", L"DbgMsgSrc.exe", L"-n", nullptr, SW_HIDE); - if (result <= HINSTANCE(32)) - { - std::cerr << "error starting DbgMsgSrc.exe\n"; - break; - } - Sleep(100); - } -} - -void CoutCerrTest() -{ - for (int i = 1; i <= 5; ++i) - { - std::cout << "========= cycle " << i << "/5 ========\n"; - std::cout << "Message on cout 1\n"; - std::cout << "Message on cout 2\n"; - std::cerr << "Message on cerr 1\n"; - std::cerr << "Message on cerr 2\n"; - std::cout << "Message on cout 3\n"; - std::cerr << "Message on cerr 3\n"; - std::cout << "Message on cout 4\n"; - std::cerr << "Message on cerr 4\n"; - std::cout << "Message on cout 5\n"; - std::cerr << "Message on cerr 5\n"; - Sleep(2000); - } -} - -void UdpTest(const std::string& address, int port) -{ - Win32::WinsockInitialization wsaInit; - - auto s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); - if (s == INVALID_SOCKET) - Win32::WSAThrowLastError("socket"); - - sockaddr_in sa; - sa.sin_family = AF_INET; - sa.sin_port = htons(u_short(port)); - sa.sin_addr.s_addr = inet_addr(address.c_str()); - - std::string message = "DbgMsgSrc UDP message"; - auto rc = sendto(s, message.data(), static_cast(message.size()), 0, reinterpret_cast(&sa), sizeof(sa)); - if (rc == SOCKET_ERROR) - Win32::WSAThrowLastError("sendto"); - - closesocket(s); -} - -void CoutCerrTest2() -{ - std::cout << "One message on cout with newline\n"; - // std::cerr << "One message on cerr with newline\n"; -} - - -void PrintUsage() -{ - std::cout << "Usage: DbgMsgSrc \n" - "\n" - " -1 read 'titan_crash_debugview_43mb.log' and output it through OutputDebugStringA\n" - " -2 read and output it through OutputDebugStringA\n" - " -3 run endless test\n" - " -s run -n repeatedly (10x / second) in separate processes)\n" - " -w Send OutputDebugStringA 'WithoutNewLine'\n" - " -n Send OutputDebugStringA 'WithNewLine\\n'\n" - " -e Send empty OutputDebugStringA message (does not trigger DBwinMutex!)\n" - // about -4: - // we cannot guarantee what happens if 1x OutputDebugStringA is send - // the process might already be gone be time we handle the message - // however, if 2 messages are send, the process is guarenteed to be alive - // at least after receiving the first message but before setting the m_dbWinBufferReady flag.. - // (because before setting the flag the traced process is still waiting for the flag) - // this means sending 2 messages and dieing ASAP afterwards is the worst-case we can still handle reliablely. - " -4 Send 2x OutputDebugStringA 'WithNewLine\\n' (process handle cache test)\n" - " -5 Send OutputDebugStringA '1\\n2\\n3\\n'\n" - " -6 Send OutputDebugStringA '1 ' '2 ' '3\\n' in separate messages\n" - " -7 DbgMsgTest, sends 5 different test lines, using different newlines styles\n" - " -8 DbgMsgSrc, Send OutputDebugStringA test lines with the specified frequency\n" - " -9 DBGVIEWCLEAR test\n" - " -A cout/cerr test\n" - " -u
Send UDP messsages to address:port\n" - " -b Send 4 lines large enough to rule out any small string optimizations (50 chars)\n" - " -c Send 2 lines with utf-8 encoded unicode (chinese characters)\n"; -} - -int Main(int argc, char* argv[]) -{ - std::cout << "DbgMsgSrc, pid: " << GetCurrentProcessId() << std::endl; - - // HANDLE handle1 = ::OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, GetCurrentProcessId()); - // HANDLE handle2 = ::OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, GetCurrentProcessId()); - // HANDLE handle3 = ::OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, GetCurrentProcessId()); - // printf(" %p, %p, %p\n", handle1, handle2, handle3); - - for (int i = 1; i < argc; ++i) - { - std::string arg(argv[i]); - if (arg == "-1") - { - TestODS("titan_crash_debugview_43mb.log"); - return 0; - } - if (arg == "-1n") - { - TestSendMessage("titan_crash_debugview_43mb.log"); - return 0; - } - else if (arg == "-2") - { - if (i + 1 < argc) - { - TestODS(argv[i + 1]); - } - else - { - PrintUsage(); - return -1; - } - return 0; - } - else if (arg == "-3") - { - EndlessTest(); - return 0; - } - else if (arg == "-s") // run separate process test - { - SeparateProcessTest(); - return 0; - } - else if (arg == "-w") - { - std::cout << "Send OutputDebugStringA 'WithoutNewLine ' (15 bytes)\n"; - OutputDebugStringA("WithoutNewLine "); - return 0; - } - else if (arg == "-n") - { - std::cout << "Send OutputDebugStringA 'With-a-NewLine \\n' (16 bytes)\n"; - OutputDebugStringA("With-a-NewLine \n"); - return 0; - } - else if (arg == "-e") - { - std::cout << "Send empty OutputDebugStringA message (0 bytes)\n"; - OutputDebugStringA(""); // empty message - return 0; - } - else if (arg == "-4") - { - std::cout << "Send 2x OutputDebugStringA 'WithNewLine\\n (24 bytes)'\n"; - OutputDebugStringA("WithNewLine\n"); - OutputDebugStringA("WithNewLine\n"); - return 0; - } - else if (arg == "-5") - { - std::cout << "Send OutputDebugStringA '1\\n2\\n3\\n' (6 bytes)\n"; - OutputDebugStringA("1\n2\n3\n"); - return 0; - } - else if (arg == "-6") - { - std::cout << "Send OutputDebugStringA '1 ' '2 ' '3\\n' in separate messages (6 bytes)\n"; - OutputDebugStringA("1 "); - OutputDebugStringA("2 "); - OutputDebugStringA("3\n"); - return 0; - } - else if (arg == "-7") - { - DbgMsgTest(); - return 7; - } - else if (arg == "-8") - { - if (i + 1 < argc) - { - DbgMsgSrc(std::stoi(argv[i + 1])); - } - else - { - PrintUsage(); - return -1; - } - return 0; - } - else if (arg == "-9") - { - DbgMsgClearTest(); - return 0; - } - else if (arg == "-A") - { - CoutCerrTest(); - return 0; - } - else if (arg == "-u") - { - if (i + 2 < argc) - { - UdpTest(argv[i + 1], std::stoi(argv[i + 2])); - return 0; - } - PrintUsage(); - return -1; - } - else if (arg == "-B") - { - CoutCerrTest2(); - return 0; - } - else if (arg == "-C") - { - SocketTest(); - return 0; - } - else if (arg == "-D") - { - OutputDebugStringA("test1\n"); - OutputDebugStringA("\n"); - OutputDebugStringA("\n\n"); - OutputDebugStringA("test2\n\n"); - OutputDebugStringA("test3\n"); - return 0; - } - else if (arg == "-b") - { - OutputDebugStringA("test long string with enough chars to exceed small string optimizations (SSO)\n"); // 79 chars + '\n' + '0'; - OutputDebugStringA("test long string with enough chars to exceed small string optimizations (SSO)\n"); // 79 chars + '\n' + '0'; - return 0; - } - else if (arg == "-c") - { - OutputDebugStringW(L"writing \u82F1\u8BED\n"); // ?? - OutputDebugStringW(L"some \u4E2D\u6587 and more \u4E2D\u6587\u4FE1\u606F\n"); // ???? - return 0; - } - else if (arg == "-no") - { - TestSendMessage("test long string with enough chars to exceed small string optimizations (SSO)\n"); - TestSendMessage("test long string with enough chars to exceed small string optimizations (SSO)\n"); - return 0; - } - else - { - TestODS(arg); - return 0; - } - } - PrintUsage(); - return EXIT_SUCCESS; -} - -} // namespace DbgMsgSrc -} // namespace fusion - -int main(int argc, char* argv[]) -try -{ - return fusion::DbgMsgSrc::Main(argc, argv); -} -catch (std::exception& ex) -{ - std::cerr << ex.what() << std::endl; - return EXIT_FAILURE; -} +// (C) Copyright Gert-Jan de Vos and Jan Wilmans 2013. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#include "dbgstream.h" +#include "Win32/Win32Lib.h" +#include "Timer.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "shellapi.h" + +#pragma comment(lib, "Ws2_32.lib") +#pragma comment(lib, "psapi.lib") + +namespace fusion { + +namespace Win32 { + +class WinsockInitialization : noncopyable +{ +public: + explicit WinsockInitialization(int major = 2, int minor = 2) + { + WSADATA wsaData = {0}; + int rc = WSAStartup(MAKEWORD(major, minor), &wsaData); + if (rc != 0) + ThrowWin32Error(rc, "WSAStartup"); + } + + ~WinsockInitialization() + { + WSACleanup(); + } +}; + +void WSAThrowLastError(const std::string& what) +{ + Win32::ThrowWin32Error(WSAGetLastError(), what); +} + +} // namespace Win32 + +namespace DbgMsgSrc { + +void DbgMsgSrc(double freq) +{ + unsigned t0 = GetTickCount(); + unsigned count = 0; + double msgPerTick = freq * 1e-3; + for (;;) + { + Sleep(10); + while (count < (GetTickCount() - t0) * msgPerTick) + { + if (count % 10 == 0) + cdbg << "Een, twee, drie, vier, vijf, zes, zeven, acht, negen, tien, elf, twaalf, dertien, veertien, vijftien, zestien, zeventien, achttien, negentien, twintig, eenentwintig, tweeentwintig, drieentwintig, vierentwintig, vijfentwintig, zesentwintig, zevenentwintig, achtentwintig, negenentwintig, dertig.\n"; + else + cdbg << "Message #" << count << "\n"; + ++count; + } + } +} + +void DbgMsgTest() +{ + OutputDebugStringA("Message 1 without newline"); + OutputDebugStringA("Message 2 without newline"); + OutputDebugStringA("Message 3 with newline\n"); + OutputDebugStringA("Message with\nembedded newline\n"); + OutputDebugStringA("This should look like a table in a non-proportionally spaced font like 'Courier'"); + OutputDebugStringA("Columnn1\tColumnn2\tColumnn3\tColumnn4\tColumnn5"); + OutputDebugStringA("1\t\t2\t\t3\t\t4\t\t5"); + OutputDebugStringA("A\t\tB\t\tC\t\tD\t\tE"); + OutputDebugStringA("11\t\t12\t\t13\t\t14\t\t15"); + OutputDebugStringA("21\t\t22\t\t23A\t\t24\t\t25"); + + OutputDebugStringA(" This line has 1 space prefixed"); + OutputDebugStringA(" This line has 2 space prefixed"); + OutputDebugStringA(" This line has 3 space prefixed"); + OutputDebugStringA(" This line has 4 space prefixed"); + + OutputDebugStringA("HighLighting test: Double-click the word 'lines' in each of the following lines and make sure that word is highlighted."); + + OutputDebugStringA("\t123\t \t123\t \t123\t \t123\t This lines starts with tab"); + OutputDebugStringA(" \t123\t \t123\t \t123\t \t123\t This lines starts with 1 space + tab"); + OutputDebugStringA(" \t123\t \t123\t \t123\t \t123\t This lines starts with 2 spaces + tab"); + OutputDebugStringA(" \t123\t \t123\t \t123\t \t123\t This lines starts with 3 spaces + tab"); + OutputDebugStringA(" \t123\t \t123\t \t123\t \t123\t This lines starts with 4 spaces + tab"); + OutputDebugStringA("2LongLine: Very Long Message that ends in a single newline. Very Long Message that ends in a single newline. Very Long Message that ends in a single newline. Very Long Message that ends in a single newline. Very Long Message that ends in a single newline. Very Long Message that ends in a single newline. Very Long Message that ends in a single newline. Very Long Message that ends in a single newline. Very Long Message that ends in a single newline. Very Long Message that ends in a single newline. Very Long Message that ends in a single newline. Very Long Message that ends in a single newline. Very Long Message that ends in a single newline. Very Long Message that ends in a single newline. Message ends HERE.\n"); + + OutputDebugStringA("Message without newline (tid: 9000)"); + OutputDebugStringA("Message without newline (tid: 9001)"); + OutputDebugStringA("Message without newline (tid: 9002)"); + OutputDebugStringA("Message without newline (tid: 9003)"); + OutputDebugStringA("Message without newline (tid: 9001)"); + OutputDebugStringA("Message without newline (tid: 9002)"); + OutputDebugStringA("Message without newline (tid: 9003)"); + OutputDebugStringA("Message without newline (tid: 9003)"); + OutputDebugStringA("Message without newline (tid: 9002)"); + OutputDebugStringA("Message without newline (tid: 9001)"); + OutputDebugStringA("Message without newline (tid: 9000)"); + OutputDebugStringA("Message without newline (address: 0x01020304)"); + OutputDebugStringA("Message without newline (address: 0xDEADBEEF)"); + OutputDebugStringA("Message without newline (address: 0xFEEDBEEF)"); + OutputDebugStringA("Message without newline (address: 0xB105F00D)"); + OutputDebugStringA("Message without newline (address: 0x0DEFACED)"); + OutputDebugStringA("Message without newline (address: 0xBAADF00D)"); + OutputDebugStringA("Message without newline (address: 0xCAFEBABE)"); + OutputDebugStringA("Message without newline (address: 0xDEADC0DE)"); + OutputDebugStringA("Message without newline (address: 0xDEADC0DE)"); +} + +void SocketTest() +{ + OutputDebugStringA("1"); + OutputDebugStringA("22"); + OutputDebugStringA("333"); + OutputDebugStringA("4444"); + OutputDebugStringA("55555"); + OutputDebugStringA("666666"); + OutputDebugStringA("1010101010"); +} + +void SendMessageTestNotepad(std::string msg) +{ + HWND notepad = FindWindowA(NULL, "Untitled - Notepad"); + HWND edit = FindWindowExA(notepad, NULL, "EDIT", NULL); + SendMessageA(edit, EM_REPLACESEL, TRUE, (LPARAM)msg.data()); +} + +void SendMessageDebugViewpp(std::string msg) +{ + HWND debugviewpp = FindWindowA(NULL, "[Capture Win32] - DebugView++"); + SendMessageA(debugviewpp, EM_REPLACESEL, GetCurrentProcessId(), (LPARAM)msg.data()); +} + +void DbgMsgClearTest() +{ + char buffer[200]; + + int line = 1; + for (int i = 0; i < 400; ++i) + { + sprintf_s(buffer, "Message %d", line); + OutputDebugStringA(buffer); + ++line; + } + + OutputDebugStringA(" ----> DBGVIEWCLEAR\n"); + + for (int i = 0; i < 10; ++i) + { + sprintf_s(buffer, "Message %d", line); + OutputDebugStringA(buffer); + ++line; + } +} + +int getMilliCount() +{ + timeb tb; + ftime(&tb); + return tb.millitm + (tb.time & 0xfffff) * 1000; +} + +void testLongString() +{ + int length = 15000; + std::ostringstream ss; + ss << "1234567890ABCDEF-%s {0} \t\r\n"; + ss << length; + std::string test = ss.str(); + for (size_t i = 0; i < length - test.size() - 1; ++i) + ss << "X"; + + ss << "\n"; + test = ss.str(); + + OutputDebugStringA(test.c_str()); +} + +std::vector GetFileContent(const std::string& filename) +{ + std::fstream fs; + fs.open(filename, std::fstream::in); + + if (!fs) + { + std::cout << "\"" << filename << "\" not found!\n"; + return {}; + } + + std::cout << "Reading " << filename << "...\n"; + std::vector lines; + std::string line; + while (getline(fs, line)) + lines.push_back(line); + fs.close(); + return lines; +} + +void TestODS(const std::string& filename) +{ + auto lines = GetFileContent(filename); + std::cout << "writing... " << lines.size() << " lines\n"; + Timer timer; + auto t1 = timer.now(); + for (const auto& s : lines) + { + OutputDebugStringA(s.c_str()); + } + auto elepsed = timer.now() - t1; + std::cout << "OutputDebugStringA " << lines.size() << " lines, took: " << static_cast(Timer::ToMs(elepsed)) << " ms\n"; +} + +void TestSendMessage(const std::string& filename) +{ + auto lines = GetFileContent(filename); + std::cout << "writing... " << lines.size() << " lines\n"; + Timer timer; + + HWND debugviewpp = FindWindow(NULL, L"[Capture Win32] - DebugView++"); + + auto t1 = timer.now(); + for (const auto& s : lines) + { + SendMessage(debugviewpp, EM_REPLACESEL, GetCurrentProcessId(), (LPARAM)s.data()); + } + auto elepsed = timer.now() - t1; + std::cout << "SendMessage " << lines.size() << " lines, took: " << static_cast(Timer::ToMs(elepsed)) << " ms\n"; +} + +void EndlessTest() +{ + int length = 40; // 260 + std::ostringstream ss; + ss << "123456:jan-7890_ABC__DEF-te m_test"; + ss << length; + std::string test = ss.str(); + for (size_t i = 0; i < length - test.size() - 1; ++i) + ss << "X"; + + ss << "\n"; + test = ss.str(); + + // testLongString(); + + for (;;) + { + long t1 = getMilliCount(); + + for (int a = 0; a < 5; ++a) + { + OutputDebugStringA(" ## before me ##\n"); + + // write exactly 2MB to the debug buffer; + for (int i = 0; i < 9; ++i) + OutputDebugStringA(test.c_str()); + + long t2 = getMilliCount(); + OutputDebugStringA(" ## tracking me ##\n"); + std::cout << "took: " << t2 - t1 << " ms\n"; + Sleep(50); + } + // OutputDebugStringA(" ----> DBGVIEWCLEAR\n"); + } +} + +void EncodingTest() +{ + const wchar_t* utf16Message = L"This message is UTF-16 encoded and send through OutputDebugStringW \u65E5\u672C\u8A9E"; // 日本語 UTF-16 encoded + OutputDebugStringW(utf16Message); + + // 日本語 😀" encoded as UTF-8 bytes + const char* utf8Message = "This message is UTF-8 encoded and send through OutputDebugStringA (with special windows settings): " + "\xE6\x97\xA5" // 日 // Ni + "\xE6\x9C\xAC" // 本 // Hon + "\xE8\xAA\x9E" // 語 // Go + " " + "\xF0\x9F\x98\x80"; // 😀 + + OutputDebugStringA(utf8Message); +} + +void SeparateProcessTest() +{ + std::cerr << "SeparateProcessTest\n"; + for (;;) + { + auto result = ShellExecute(nullptr, L"open", L"DbgMsgSrc.exe", L"-n", nullptr, SW_HIDE); + if (result <= HINSTANCE(32)) + { + std::cerr << "error starting DbgMsgSrc.exe\n"; + break; + } + Sleep(100); + } +} + +void CoutCerrTest() +{ + for (int i = 1; i <= 5; ++i) + { + std::cout << "========= cycle " << i << "/5 ========\n"; + std::cout << "Message on cout 1\n"; + std::cout << "Message on cout 2\n"; + std::cerr << "Message on cerr 1\n"; + std::cerr << "Message on cerr 2\n"; + std::cout << "Message on cout 3\n"; + std::cerr << "Message on cerr 3\n"; + std::cout << "Message on cout 4\n"; + std::cerr << "Message on cerr 4\n"; + std::cout << "Message on cout 5\n"; + std::cerr << "Message on cerr 5\n"; + Sleep(2000); + } +} + +void UdpTest(const std::string& address, int port) +{ + Win32::WinsockInitialization wsaInit; + + auto s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (s == INVALID_SOCKET) + Win32::WSAThrowLastError("socket"); + + sockaddr_in sa; + sa.sin_family = AF_INET; + sa.sin_port = htons(u_short(port)); + sa.sin_addr.s_addr = inet_addr(address.c_str()); + + std::string message = "DbgMsgSrc UDP message"; + auto rc = sendto(s, message.data(), static_cast(message.size()), 0, reinterpret_cast(&sa), sizeof(sa)); + if (rc == SOCKET_ERROR) + Win32::WSAThrowLastError("sendto"); + + closesocket(s); +} + +void CoutCerrTest2() +{ + std::cout << "One message on cout with newline\n"; + // std::cerr << "One message on cerr with newline\n"; +} + + +void PrintUsage() +{ + std::cout << "Usage: DbgMsgSrc \n" + "\n" + " -1 read 'titan_crash_debugview_43mb.log' and output it through OutputDebugStringA\n" + " -2 read and output it through OutputDebugStringA\n" + " -3 run endless test\n" + " -s run -n repeatedly (10x / second) in separate processes)\n" + " -w Send OutputDebugStringA 'WithoutNewLine'\n" + " -n Send OutputDebugStringA 'WithNewLine\\n'\n" + " -e Send empty OutputDebugStringA message (does not trigger DBwinMutex!)\n" + // about -4: + // we cannot guarantee what happens if 1x OutputDebugStringA is send + // the process might already be gone be time we handle the message + // however, if 2 messages are send, the process is guarenteed to be alive + // at least after receiving the first message but before setting the m_dbWinBufferReady flag.. + // (because before setting the flag the traced process is still waiting for the flag) + // this means sending 2 messages and dieing ASAP afterwards is the worst-case we can still handle reliablely. + " -4 Send 2x OutputDebugStringA 'WithNewLine\\n' (process handle cache test)\n" + " -5 Send OutputDebugStringA '1\\n2\\n3\\n'\n" + " -6 Send OutputDebugStringA '1 ' '2 ' '3\\n' in separate messages\n" + " -7 DbgMsgTest, sends 5 different test lines, using different newlines styles\n" + " -8 DbgMsgSrc, Send OutputDebugStringA test lines with the specified frequency\n" + " -9 DBGVIEWCLEAR test\n" + " -A cout/cerr test\n" + " -u
Send UDP messsages to address:port\n" + " -b Send 4 lines large enough to rule out any small string optimizations (50 chars)\n" + " -c Send 2 lines with utf-8 encoded unicode (chinese characters)\n"; +} + +int Main(int argc, char* argv[]) +{ + std::cout << "DbgMsgSrc, pid: " << GetCurrentProcessId() << std::endl; + + // HANDLE handle1 = ::OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, GetCurrentProcessId()); + // HANDLE handle2 = ::OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, GetCurrentProcessId()); + // HANDLE handle3 = ::OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, GetCurrentProcessId()); + // printf(" %p, %p, %p\n", handle1, handle2, handle3); + + for (int i = 1; i < argc; ++i) + { + std::string arg(argv[i]); + if (arg == "-1") + { + TestODS("titan_crash_debugview_43mb.log"); + return 0; + } + if (arg == "-1n") + { + TestSendMessage("titan_crash_debugview_43mb.log"); + return 0; + } + else if (arg == "-2") + { + if (i + 1 < argc) + { + TestODS(argv[i + 1]); + } + else + { + PrintUsage(); + return -1; + } + return 0; + } + else if (arg == "-3") + { + EndlessTest(); + return 0; + } + else if (arg == "-4") + { + EncodingTest(); + return 0; + } + else if (arg == "-s") // run separate process test + { + SeparateProcessTest(); + return 0; + } + else if (arg == "-w") + { + std::cout << "Send OutputDebugStringA 'WithoutNewLine ' (15 bytes)\n"; + OutputDebugStringA("WithoutNewLine "); + return 0; + } + else if (arg == "-n") + { + std::cout << "Send OutputDebugStringA 'With-a-NewLine \\n' (16 bytes)\n"; + OutputDebugStringA("With-a-NewLine \n"); + return 0; + } + else if (arg == "-e") + { + std::cout << "Send empty OutputDebugStringA message (0 bytes)\n"; + OutputDebugStringA(""); // empty message + return 0; + } + else if (arg == "-4") + { + std::cout << "Send 2x OutputDebugStringA 'WithNewLine\\n (24 bytes)'\n"; + OutputDebugStringA("WithNewLine\n"); + OutputDebugStringA("WithNewLine\n"); + return 0; + } + else if (arg == "-5") + { + std::cout << "Send OutputDebugStringA '1\\n2\\n3\\n' (6 bytes)\n"; + OutputDebugStringA("1\n2\n3\n"); + return 0; + } + else if (arg == "-6") + { + std::cout << "Send OutputDebugStringA '1 ' '2 ' '3\\n' in separate messages (6 bytes)\n"; + OutputDebugStringA("1 "); + OutputDebugStringA("2 "); + OutputDebugStringA("3\n"); + return 0; + } + else if (arg == "-7") + { + DbgMsgTest(); + return 7; + } + else if (arg == "-8") + { + if (i + 1 < argc) + { + DbgMsgSrc(std::stoi(argv[i + 1])); + } + else + { + PrintUsage(); + return -1; + } + return 0; + } + else if (arg == "-9") + { + DbgMsgClearTest(); + return 0; + } + else if (arg == "-A") + { + CoutCerrTest(); + return 0; + } + else if (arg == "-u") + { + if (i + 2 < argc) + { + UdpTest(argv[i + 1], std::stoi(argv[i + 2])); + return 0; + } + PrintUsage(); + return -1; + } + else if (arg == "-B") + { + CoutCerrTest2(); + return 0; + } + else if (arg == "-C") + { + SocketTest(); + return 0; + } + else if (arg == "-D") + { + OutputDebugStringA("test1\n"); + OutputDebugStringA("\n"); + OutputDebugStringA("\n\n"); + OutputDebugStringA("test2\n\n"); + OutputDebugStringA("test3\n"); + return 0; + } + else if (arg == "-b") + { + OutputDebugStringA("test long string with enough chars to exceed small string optimizations (SSO)\n"); // 79 chars + '\n' + '0'; + OutputDebugStringA("test long string with enough chars to exceed small string optimizations (SSO)\n"); // 79 chars + '\n' + '0'; + return 0; + } + else if (arg == "-c") + { + OutputDebugStringW(L"writing \u82F1\u8BED\n"); // ?? + OutputDebugStringW(L"some \u4E2D\u6587 and more \u4E2D\u6587\u4FE1\u606F\n"); // ???? + return 0; + } + else if (arg == "-no") + { + TestSendMessage("test long string with enough chars to exceed small string optimizations (SSO)\n"); + TestSendMessage("test long string with enough chars to exceed small string optimizations (SSO)\n"); + return 0; + } + else + { + TestODS(arg); + return 0; + } + } + PrintUsage(); + return EXIT_SUCCESS; +} + +} // namespace DbgMsgSrc +} // namespace fusion + +int main(int argc, char* argv[]) +try +{ + return fusion::DbgMsgSrc::Main(argc, argv); +} +catch (std::exception& ex) +{ + std::cerr << ex.what() << std::endl; + return EXIT_FAILURE; +} diff --git a/application/DebugViewConsole/CMakeLists.txt b/application/DebugViewConsole/CMakeLists.txt index cf0d6976..cde25119 100644 --- a/application/DebugViewConsole/CMakeLists.txt +++ b/application/DebugViewConsole/CMakeLists.txt @@ -3,15 +3,15 @@ project(DebugViewConsole) add_executable(DebugViewConsole DebugViewConsole.cpp) target_link_libraries(DebugViewConsole - PRIVATE - project::definitions - project::compile_features - project::compile_options - nuget::boost_test - dv::library - dv::indexedstorage - CobaltFusion - docopt +PRIVATE + project::definitions + project::compile_features + project::compile_options + nuget::boost_test + dv::library + dv::indexedstorage + CobaltFusion + docopt ) install(TARGETS DebugViewConsole RUNTIME DESTINATION bin) @@ -21,16 +21,14 @@ target_include_directories(DebugViewConsole PUBLIC ) add_custom_command( - TARGET DebugViewConsole - POST_BUILD + TARGET DebugViewConsole + POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy $ ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/DebugViewConsole-small.exe -) -install(CODE " - execute_process(COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/DebugViewConsole.exe ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/DebugViewConsole-small.exe) - execute_process(COMMAND ${PROJECT_UTILS_DIRECTORY}/upx.exe ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/DebugViewConsole-small.exe) -") + COMMAND ${PROJECT_UTILS_DIRECTORY}/upx.exe + ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/DebugViewConsole-small.exe +) -INSTALL(FILES ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/DebugViewConsole-small.exe DESTINATION bin) \ No newline at end of file +install(FILES $/DebugViewConsole-small.exe DESTINATION bin) diff --git a/application/DebugViewpp/CMakeLists.txt b/application/DebugViewpp/CMakeLists.txt index 6d44688a..b6a3e0b2 100644 --- a/application/DebugViewpp/CMakeLists.txt +++ b/application/DebugViewpp/CMakeLists.txt @@ -22,19 +22,19 @@ add_executable(Debugviewpp ) target_link_libraries(Debugviewpp - PRIVATE - project::definitions - project::compile_features - project::compile_options - project::gui_link_options - nuget::boost - nuget::wtl - dv::cobaltfusion - dv::tabbingframework - dv::propertygrid - dv::indexedstorage - dv::library - dv::win32 +PRIVATE + project::definitions + project::compile_features + project::compile_options + project::gui_link_options + nuget::boost + nuget::wtl + dv::cobaltfusion + dv::tabbingframework + dv::propertygrid + dv::indexedstorage + dv::library + dv::win32 ) install(TARGETS Debugviewpp DESTINATION bin) @@ -43,9 +43,15 @@ target_include_directories(Debugviewpp PUBLIC $ ) -install(CODE " - execute_process(COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Debugviewpp.exe ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Debugviewpp-small.exe) - execute_process(COMMAND ${PROJECT_UTILS_DIRECTORY}/upx.exe ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Debugviewpp-small.exe) -") +add_custom_command( + TARGET Debugviewpp + POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + $ + ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Debugviewpp-small.exe -INSTALL(FILES ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Debugviewpp-small.exe DESTINATION bin) + COMMAND ${PROJECT_UTILS_DIRECTORY}/upx.exe + ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Debugviewpp-small.exe +) + +install(FILES $/Debugviewpp-small.exe DESTINATION bin) diff --git a/application/DebugViewpp/DebugView++.cpp b/application/DebugViewpp/DebugView++.cpp index 1d739b45..3806bd49 100644 --- a/application/DebugViewpp/DebugView++.cpp +++ b/application/DebugViewpp/DebugView++.cpp @@ -12,17 +12,16 @@ #include "DebugViewppLib/DBWinWriter.h" #include "Win32/Com.h" -#include - #include "atleverything.h" -//#define ENABLE_CRASHPAD +#include +#include // for std::getenv + +// #define ENABLE_CRASHPAD #ifdef ENABLE_CRASHPAD #include "crashpad.h" #endif -//#define CONSOLE_DEBUG - CAppModule _Module; namespace fusion { @@ -90,10 +89,32 @@ int ForwardMessagesFromPipe(HANDLE hPipe) return 0; } +class DebugConsole +{ +public: + DebugConsole() + { + ::AllocConsole(); + ::freopen_s(&standardOut, "CONOUT$", "wb", stdout); + std::cout.clear(); + } + + ~DebugConsole() + { + fclose(standardOut); + fclose(stdout); + FreeConsole(); + } + +private: + FILE* standardOut = nullptr; +}; + int Main(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, LPWSTR /*lpstrCmdLine*/, int cmdShow) { Win32::SetPrivilege(SE_DEBUG_NAME, true); Win32::SetPrivilege(SE_CREATE_GLOBAL_NAME, true); + Win32::SetPrivilege(SE_LOAD_DRIVER_NAME, true); Win32::ComInitialization com; @@ -102,13 +123,11 @@ int Main(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, LPWSTR /*lpstrCmdLine AtlInitCommonControls(ICC_BAR_CLASSES); // add flags to support other controls -#ifdef CONSOLE_DEBUG - FILE* standardOut; - AllocConsole(); - freopen_s(&standardOut, "CONOUT$", "wb", stdout); - auto fileGuard = make_guard([standardOut] { fclose(standardOut); }); - std::cout.clear(); -#endif + std::unique_ptr debugConsole; + if (std::getenv("DEBUGVIEWPP_CONSOLE_DEBUG") != nullptr) + { + debugConsole = std::make_unique(); + } CAppModuleInitialization moduleInit(_Module, hInstance); @@ -132,6 +151,7 @@ int Main(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, LPWSTR /*lpstrCmdLine auto args = Win32::GetCommandLineArguments(); std::wstring fileName; + std::wstring selectTabName; for (size_t i = 1; i < args.size(); ++i) { @@ -141,8 +161,12 @@ int Main(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, LPWSTR /*lpstrCmdLine } else if (boost::iequals(args[i], L"/log")) { - //wndMain.SetLogging(); // todo: implement: FileWriter needs to concurrently access m_logfile, it now causes a crash if DbgMsgSrc -1 is run - // this should be replaced by the new streaming-to-disk feature we discussed. + // wndMain.SetLogging(); // todo: implement: FileWriter needs to concurrently access m_logfile, it now causes a crash if DbgMsgSrc -1 is run + // this should be replaced by the new streaming-to-disk feature we discussed. + } + else if (boost::icontains(args[i], L"/tab:")) + { + selectTabName = args[i].substr(5); } else if (args[i][0] != '/') { @@ -177,6 +201,11 @@ int Main(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, LPWSTR /*lpstrCmdLine wndMain.CapturePipe(hPipe); } + if (!selectTabName.empty()) + { + wndMain.SetSelectTabByName(selectTabName); + } + return theLoop.Run(); } @@ -184,7 +213,8 @@ int Main(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, LPWSTR /*lpstrCmdLine } // namespace fusion -int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpstrCmdLine, int nCmdShow) try +int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpstrCmdLine, int nCmdShow) +try { #ifdef ENABLE_CRASHPAD initializeCrashPad(); diff --git a/application/DebugViewpp/DebugView++.rc b/application/DebugViewpp/DebugView++.rc index 2ba769b8..f4fbac73 100644 --- a/application/DebugViewpp/DebugView++.rc +++ b/application/DebugViewpp/DebugView++.rc @@ -26,19 +26,19 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US // TEXTINCLUDE // -1 TEXTINCLUDE +1 TEXTINCLUDE BEGIN "resource.h\0" END -2 TEXTINCLUDE +2 TEXTINCLUDE BEGIN "#include ""atlres.h""\r\n" "#include ""verrsrc.h""\r\n" "\0" END -3 TEXTINCLUDE +3 TEXTINCLUDE BEGIN "#include ""Version.rc2""\r\n" "\0" @@ -111,6 +111,9 @@ BEGIN MENUITEM "Crop Log to &Selection", ID_LOG_CROP MENUITEM "&Pause\tPause", ID_LOG_PAUSE MENUITEM "Capture &Global Win32", ID_LOG_GLOBAL + MENUITEM "Capture &Kernel Messages", ID_LOG_KERNEL + MENUITEM "&Verbose Kernel Messages", ID_LOG_KERNEL_VERBOSE + MENUITEM "Pass-&Through mode", ID_LOG_KERNEL_PASSTHROUGH MENUITEM "Connect DebugView &Agent", ID_LOG_DEBUGVIEW_AGENT MENUITEM "Sources...", ID_LOG_SOURCES MENUITEM "History Size...", ID_LOG_HISTORY @@ -245,6 +248,12 @@ IDR_MAINFRAME ICON "res\\DebugView++.ico" IDR_BOOKMARK ICON "res\\Bookmark.ico" +///////////////////////////////////////////////////////////////////////////// +// +// RCDATA +// + +IDR_DBGV_DRIVER RCDATA "res\\Dbgv.sys" ///////////////////////////////////////////////////////////////////////////// // @@ -615,6 +624,9 @@ STRINGTABLE BEGIN ID_LOG_PAUSE "Pause log recording\nPause" ID_LOG_GLOBAL "Capture logs from all processes\nCapture Global Win32" + ID_LOG_KERNEL "Capture logs from kernel processes\nCapture Kernel Messages" + ID_LOG_KERNEL_VERBOSE "Enable verbose kernel messsages\nVerbose Kernel Messages" + ID_LOG_KERNEL_PASSTHROUGH "Enable Pass-throught mode\nPass-Through mode" ID_LOG_DEBUGVIEW_AGENT "Connect DbgView Agent\nConnect DbgView Agent" ID_LOG_HISTORY "Configure log file history size\nConfigure History Size" ID_VIEW_CLEAR "Clear log view\nClear View" diff --git a/application/DebugViewpp/DropTargetSupport.cpp b/application/DebugViewpp/DropTargetSupport.cpp index 4b26922f..e183f46b 100644 --- a/application/DebugViewpp/DropTargetSupport.cpp +++ b/application/DebugViewpp/DropTargetSupport.cpp @@ -18,7 +18,6 @@ namespace debugviewpp { */ DropTargetSupport::DropTargetSupport() - { } diff --git a/application/DebugViewpp/FilterPage.cpp b/application/DebugViewpp/FilterPage.cpp index fd568929..66fa4943 100644 --- a/application/DebugViewpp/FilterPage.cpp +++ b/application/DebugViewpp/FilterPage.cpp @@ -49,6 +49,7 @@ bool SupportsColor(FilterType::type filterType) switch (filterType) { case FilterType::Exclude: + case FilterType::Include: return false; default: return true; diff --git a/application/DebugViewpp/LogView.cpp b/application/DebugViewpp/LogView.cpp index ad136775..77b6bda6 100644 --- a/application/DebugViewpp/LogView.cpp +++ b/application/DebugViewpp/LogView.cpp @@ -2107,7 +2107,6 @@ bool FilterSupportsColor(FilterType::type value) { switch (value) { - case FilterType::Include: case FilterType::Highlight: case FilterType::Track: case FilterType::Stop: diff --git a/application/DebugViewpp/MainFrame.cpp b/application/DebugViewpp/MainFrame.cpp index 452d0db5..1d96f9ea 100644 --- a/application/DebugViewpp/MainFrame.cpp +++ b/application/DebugViewpp/MainFrame.cpp @@ -7,6 +7,8 @@ #include #include #include +#include +#include #include #include @@ -94,7 +96,7 @@ std::wstring FormatDuration(double seconds) return wstringbuilder() << FormatUnits(minutes, L"minute") << L" " << FormatUnits(FloorTo(seconds), L"second"); } - static const wchar_t* units[] = {L"s", L"ms", L"µs", L"ns", nullptr}; + static const wchar_t* units[] = {L"s", L"ms", L"\u03BCs", L"ns", nullptr}; // visualstudio is apparently unable to display the “μs” (microseconds) in UTF-8 encoded files const wchar_t** unit = units; while (*unit != nullptr && seconds > 0 && seconds < 1) { @@ -129,6 +131,9 @@ BEGIN_MSG_MAP2(CMainFrame) COMMAND_ID_HANDLER_EX(ID_LOG_CROP, OnLogCrop) COMMAND_ID_HANDLER_EX(ID_LOG_PAUSE, OnLogPause) COMMAND_ID_HANDLER_EX(ID_LOG_GLOBAL, OnLogGlobal) + COMMAND_ID_HANDLER_EX(ID_LOG_KERNEL, OnLogKernel) + COMMAND_ID_HANDLER_EX(ID_LOG_KERNEL_VERBOSE, OnLogKernelVerbose) + COMMAND_ID_HANDLER_EX(ID_LOG_KERNEL_PASSTHROUGH, OnLogKernelPassThrough) COMMAND_ID_HANDLER_EX(ID_LOG_HISTORY, OnLogHistory) COMMAND_ID_HANDLER_EX(ID_LOG_DEBUGVIEW_AGENT, OnLogDebugviewAgent) COMMAND_ID_HANDLER_EX(ID_VIEW_FIND, OnViewFind) @@ -189,7 +194,10 @@ CMainFrame::CMainFrame() : m_notifyIconData.cbSize = 0; } -CMainFrame::~CMainFrame() = default; +CMainFrame::~CMainFrame() +{ + RemoveDriver(); +} void CMainFrame::SetLogging() { @@ -226,7 +234,7 @@ LRESULT CMainFrame::OnCreate(const CREATESTRUCT* /*pCreate*/) HWND hWndCmdBar = m_cmdBar.Create(*this, rcDefault, nullptr, ATL_SIMPLE_CMDBAR_PANE_STYLE); m_cmdBar.AttachMenu(GetMenu()); m_cmdBar.LoadImages(IDR_MAINFRAME); - SetMenu(nullptr); //disable second menu bar + SetMenu(nullptr); // disable second menu bar CreateSimpleReBar(ATL_SIMPLE_REBAR_NOBORDER_STYLE); CReBarCtrl rebar(m_hWndToolBar); @@ -281,11 +289,6 @@ void CMainFrame::OnClose() Shell_NotifyIcon(NIM_DELETE, &m_notifyIconData); m_notifyIconData.cbSize = 0; } - -#ifdef CONSOLE_DEBUG - fclose(stdout); - FreeConsole(); -#endif } LRESULT CMainFrame::OnQueryEndSession(WPARAM /*unused*/, LPARAM /*unused*/) @@ -328,6 +331,11 @@ void CMainFrame::UpdateUI() UISetCheck(ID_LOG_PAUSE, !m_pLocalReader); UIEnable(ID_LOG_GLOBAL, !!m_pLocalReader); UISetCheck(ID_LOG_GLOBAL, m_tryGlobal); + UISetCheck(ID_LOG_KERNEL, m_tryKernel); + UIEnable(ID_LOG_KERNEL_VERBOSE, !!m_pKernelReader); + UIEnable(ID_LOG_KERNEL_PASSTHROUGH, !!m_pKernelReader); + UISetCheck(ID_LOG_KERNEL_VERBOSE, m_verboseKernelMessage); + UISetCheck(ID_LOG_KERNEL_PASSTHROUGH, m_passthroughMode); } std::wstring FormatDateTime(const SYSTEMTIME& systemTime) @@ -450,11 +458,9 @@ bool CMainFrame::OnUpdate() int count = 0; for (auto&& line : lines) { - if (count++ < 5000) - { - bucket.emplace_back(std::move(line)); - } - else + ++count; + bucket.emplace_back(std::move(line)); + if (count == 5000) { count = 0; m_incomingMessages.emplace_back(std::move(bucket)); @@ -471,7 +477,6 @@ bool CMainFrame::OnUpdate() return false; } - auto linesbucket = std::move(m_incomingMessages.front()); m_incomingMessages.pop_front(); ProcessLines(linesbucket); @@ -1224,6 +1229,19 @@ void CMainFrame::Load(std::istream& file, const std::string& name, FILETIME file } } +void CMainFrame::SetSelectTabByName(const std::wstring& tabName) +{ + const int views = GetViewCount(); + for (int i = 0; i < views; ++i) + { + if (GetView(i).GetName() == tabName) + { + GetTabCtrl().SetCurSel(i); + break; + } + } +} + void CMainFrame::CapturePipe(HANDLE hPipe) { m_logSources.AddPipeReader(Win32::GetParentProcessId(), hPipe); @@ -1397,9 +1415,32 @@ void CMainFrame::Pause() m_logSources.Remove(m_pGlobalReader); m_pGlobalReader = nullptr; } + if (m_pKernelReader != nullptr) + { + m_logSources.Remove(m_pKernelReader); + m_pKernelReader = nullptr; + } m_logSources.AddMessage(""); } +void CMainFrame::UpdateTitle() +{ + std::wstring title = L"Paused"; + if ((m_pLocalReader != nullptr) && (m_pGlobalReader != nullptr)) + { + title = L"Capture Win32 & Global Win32 Messages"; + } + else if (m_pLocalReader != nullptr) + { + title = L"Capture Win32"; + } + else if (m_pGlobalReader != nullptr) + { + title = L"Capture Global Win32"; + } + SetTitle(title); +} + void CMainFrame::Resume() { SetTitle(); @@ -1420,7 +1461,7 @@ void CMainFrame::Resume() } } - if (m_tryGlobal) + if (m_tryGlobal && m_pGlobalReader == nullptr) { try { @@ -1439,20 +1480,26 @@ void CMainFrame::Resume() } } - std::wstring title = L"Paused"; - if ((m_pLocalReader != nullptr) && (m_pGlobalReader != nullptr)) - { - title = L"Capture Win32 & Global Win32 Messages"; - } - else if (m_pLocalReader != nullptr) - { - title = L"Capture Win32"; - } - else if (m_pGlobalReader != nullptr) + if (m_tryKernel && m_pKernelReader == nullptr) { - title = L"Capture Global Win32"; + try + { + m_pKernelReader = m_logSources.AddKernelReader(); + } + catch (std::exception& e) + { + const auto message = std::format("Unable to capture Kernel Messages.\n" + "({})\n\n" + "Make sure you have appropriate permissions.\n" + "\n" + "You may need to start this application by right-clicking it and selecting\n" + "'Run As Administator' even if you have administrator rights.", + e.what()); + MessageBox(WStr(message), m_applicationName.c_str(), MB_ICONERROR | MB_OK); + m_tryKernel = false; + } } - SetTitle(title); + UpdateTitle(); } void CMainFrame::OnLogPause(UINT /*uNotifyCode*/, int /*nID*/, CWindow /*wndCtl*/) @@ -1471,7 +1518,7 @@ void CMainFrame::OnLogGlobal(UINT /*uNotifyCode*/, int /*nID*/, CWindow /*wndCtl { m_tryGlobal = (m_pGlobalReader == nullptr); - if ((m_pLocalReader != nullptr) && m_tryGlobal) + if (m_tryGlobal) { Resume(); } @@ -1480,6 +1527,80 @@ void CMainFrame::OnLogGlobal(UINT /*uNotifyCode*/, int /*nID*/, CWindow /*wndCtl m_logSources.Remove(m_pGlobalReader); m_pGlobalReader = nullptr; } + UpdateTitle(); +} + +void CMainFrame::WriteDriverFromResource() +{ + if (std::filesystem::exists(m_driverLocation)) + { + return; + } + HRSRC hRes = FindResource(NULL, MAKEINTRESOURCE(IDR_DBGV_DRIVER), RT_RCDATA); + if (hRes) + { + HGLOBAL hLoadedRes = LoadResource(NULL, hRes); + if (hLoadedRes) + { + DWORD dwSize = SizeofResource(NULL, hRes); + void* pLockedRes = LockResource(hLoadedRes); + if (pLockedRes) + { + std::cout << "Write " << m_driverLocation << "\n"; + std::ofstream outFile(m_driverLocation.c_str(), std::ios::binary); + outFile.write(static_cast(pLockedRes), dwSize); + outFile.close(); + } + } + } + + if (!std::filesystem::exists(m_driverLocation)) + { + std::cout << "Could not write to " << m_driverLocation << "\n"; + m_logSources.AddMessage("Unable to access kernel messages driver"); + } +} + +void CMainFrame::RemoveDriver() +{ + // can fail silently + std::remove(m_driverLocation.c_str()); +} + +void CMainFrame::OnLogKernel(UINT /*uNotifyCode*/, int /*nID*/, CWindow /*wndCtl*/) +{ + m_tryKernel = (m_pKernelReader == nullptr); + + if (m_tryKernel) + { + WriteDriverFromResource(); + Resume(); + } + else + { + m_logSources.Remove(m_pKernelReader); + m_pKernelReader = nullptr; + RemoveDriver(); + } + UpdateTitle(); +} + +void CMainFrame::OnLogKernelVerbose(UINT /*uNotifyCode*/, int /*nID*/, CWindow /*wndCtl*/) +{ + m_verboseKernelMessage = !m_verboseKernelMessage; + if (m_pKernelReader) + { + m_pKernelReader->SetVerbose(m_verboseKernelMessage); + } +} + +void CMainFrame::OnLogKernelPassThrough(UINT /*uNotifyCode*/, int /*nID*/, CWindow /*wndCtl*/) +{ + m_passthroughMode = !m_passthroughMode; + if (m_pKernelReader) + { + m_pKernelReader->SetPassThrough(m_passthroughMode); + } } void CMainFrame::OnLogHistory(UINT /*uNotifyCode*/, int /*nID*/, CWindow /*wndCtl*/) @@ -1553,10 +1674,10 @@ void CMainFrame::OnViewDuplicate(UINT /*uNotifyCode*/, int /*nID*/, CWindow /*wn AddFilterView(dlg.GetName(), dlg.GetFilters()); - //auto &view = GetView(); - //auto newLogView = std::make_shared(view); // no copy constructor available - //newLogView->SetName(view.GetName() + L" (copy)"); - //AddFilterView(newLogView); + // auto &view = GetView(); + // auto newLogView = std::make_shared(view); // no copy constructor available + // newLogView->SetName(view.GetName() + L" (copy)"); + // AddFilterView(newLogView); SaveSettings(); } diff --git a/application/DebugViewpp/MainFrame.h b/application/DebugViewpp/MainFrame.h index a7194024..bfebbccb 100644 --- a/application/DebugViewpp/MainFrame.h +++ b/application/DebugViewpp/MainFrame.h @@ -27,6 +27,7 @@ using ATL::CString; #include "CobaltFusion/ExecutorClient.h" #include "DebugViewppLib/DBWinBuffer.h" #include "DebugViewppLib/DBWinReader.h" +#include "DebugViewppLib/KernelReader.h" #include "DebugViewppLib/LineBuffer.h" #include "DebugViewppLib/LogSources.h" #include "DebugViewppLib/FileWriter.h" @@ -43,6 +44,8 @@ class DbgviewReader; using SelectedTabItem = CLogViewTabItem; +void RemoveDriver(); + class CMainFrame : public CTabbedFrameImpl>, public CUpdateUI, public ExceptionHandler, @@ -60,6 +63,9 @@ class CMainFrame : public CTabbedFrameImpl m_sourceInfos; std::unique_ptr m_GuiExecutorClient; @@ -223,6 +241,7 @@ class CMainFrame : public CTabbedFrameImpl m_incomingMessages; int m_showCmd = SW_SHOWDEFAULT; + std::string m_driverLocation = GetDebugviewDriverLocation(); }; } // namespace debugviewpp diff --git a/application/DebugViewpp/res/Dbgv.sys b/application/DebugViewpp/res/Dbgv.sys new file mode 100644 index 00000000..70316ba0 Binary files /dev/null and b/application/DebugViewpp/res/Dbgv.sys differ diff --git a/application/DebugViewpp/resource.h b/application/DebugViewpp/resource.h index f63d962e..886bed9f 100644 --- a/application/DebugViewpp/resource.h +++ b/application/DebugViewpp/resource.h @@ -16,7 +16,8 @@ #define IDC_DEBUGVIEW_SRC_URL 130 #define IDC_DEBUGVIEW_URL 131 #define IDR_BOOKMARK 132 -#define IDS_LASTVALUE 133 +#define IDR_DBGV_DRIVER 133 +#define IDS_LASTVALUE 134 #define IDD_SOURCES 199 #define IDD_FILTER 200 #define IDR_TAB_CONTEXTMENU 201 @@ -125,14 +126,19 @@ #define ID_VIEW_COPY_MESSAGES 32857 #define ID_VIEW_PROCESS_RENAME 32858 #define ID_OPTIONS_PROCESS_PREFIX 32859 +#define ID_LOG_KERNEL 32860 +#define ID_LOG_KERNEL_VERBOSE 32861 +#define ID_LOG_KERNEL_PASSTHROUGH 32862 + // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 401 -#define _APS_NEXT_COMMAND_VALUE 32857 +#define _APS_NEXT_COMMAND_VALUE 32863 #define _APS_NEXT_CONTROL_VALUE 801 #define _APS_NEXT_SYMED_VALUE 107 #endif #endif + diff --git a/application/DebugViewpp/version.h b/application/DebugViewpp/version.h index 4152cc86..ced0201d 100644 --- a/application/DebugViewpp/version.h +++ b/application/DebugViewpp/version.h @@ -1,2 +1,2 @@ -#define VERSION 1,9,0,56 -#define VERSION_STR "1.9.0.56" +#define VERSION 1, 20, 0, 0 +#define VERSION_STR "1.20.0.0" diff --git a/application/DebugViewpp/version.wxi b/application/DebugViewpp/version.wxi index 525bce22..acfd7791 100644 --- a/application/DebugViewpp/version.wxi +++ b/application/DebugViewpp/version.wxi @@ -1,8 +1,8 @@ - - - - - - - - + + + + + + + + diff --git a/application/DebugViewppLib/BinaryFileReader.cpp b/application/DebugViewppLib/BinaryFileReader.cpp index c8f81066..5e7c4f94 100644 --- a/application/DebugViewppLib/BinaryFileReader.cpp +++ b/application/DebugViewppLib/BinaryFileReader.cpp @@ -130,7 +130,7 @@ boost::signals2::connection BinaryFileReader::SubscribeToUpdate(UpdateSignal::sl // todo: Reading support for more filetypes, maybe not, who logs in unicode anyway? // see #107 -// posepone until we have a valid usecase +// postpone until we have a valid usecase // ANSI/ASCII // UTF-8 // UTF-16 diff --git a/application/DebugViewppLib/CMakeLists.txt b/application/DebugViewppLib/CMakeLists.txt index f9fa6ac6..8d7861dc 100644 --- a/application/DebugViewppLib/CMakeLists.txt +++ b/application/DebugViewppLib/CMakeLists.txt @@ -7,6 +7,7 @@ add_library(${PROJECT_NAME} Conversions.cpp CTimelineView.cpp DbgviewReader.cpp + Debugview_kernel_client.cpp DBWinBuffer.cpp DBWinReader.cpp DBWinWriter.cpp @@ -15,6 +16,7 @@ add_library(${PROJECT_NAME} FileWriter.cpp Filter.cpp FilterType.cpp + KernelReader.cpp Line.cpp LineBuffer.cpp LogFile.cpp @@ -39,14 +41,14 @@ add_library(${PROJECT_NAME} add_library(dv::library ALIAS ${PROJECT_NAME}) target_link_libraries(${PROJECT_NAME} - PRIVATE - project::definitions - project::compile_features - project::compile_options - nuget::boost - nuget::wtl - dv::cobaltfusion - dv::win32 +PRIVATE + project::definitions + project::compile_features + project::compile_options + nuget::boost + nuget::wtl + dv::cobaltfusion + dv::win32 ) target_include_directories(${PROJECT_NAME} PUBLIC diff --git a/application/DebugViewppLib/CTimelineView.cpp b/application/DebugViewppLib/CTimelineView.cpp index 6d310afb..cca44ee7 100644 --- a/application/DebugViewppLib/CTimelineView.cpp +++ b/application/DebugViewppLib/CTimelineView.cpp @@ -1,236 +1,236 @@ -// (C) Copyright Gert-Jan de Vos and Jan Wilmans 2016. -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE_1_0.txt or copy at -// http://www.boost.org/LICENSE_1_0.txt) - -#include "DebugViewppLib/CTimelineView.h" -#include -#include -#include -#include "CobaltFusion/dbgstream.h" -#include "CobaltFusion/Str.h" -#include "CobaltFusion/stringbuilder.h" -#include - -namespace fusion { -namespace gdi { - -Artifact::Artifact(Pixel position, Artifact::Type type) : - m_position(position), - m_type(type), - m_color(RGB(0, 0, 0)), - m_fillcolor(RGB(255, 255, 255)) -{ -} - -Artifact::Artifact(Pixel position, Artifact::Type type, COLORREF color) : - m_position(position), - m_type(type), - m_color(color), - m_fillcolor(RGB(255, 255, 255)) -{ -} - -Artifact::Artifact(Pixel position, Artifact::Type type, COLORREF color, COLORREF fillcolor) : - m_position(position), - m_type(type), - m_color(color), - m_fillcolor(fillcolor) -{ -} - -void Artifact::SetColor(COLORREF color) -{ - m_color = color; -} - -void Artifact::SetFillColor(COLORREF color) -{ - m_fillcolor = color; -} - -COLORREF Artifact::GetColor() const -{ - return m_color; -} - -COLORREF Artifact::GetFillColor() const -{ - return m_fillcolor; -} - -Pixel Artifact::GetPosition() const -{ - return m_position; -} - -Line::Line(const std::wstring& name) : - m_name(name) -{ -} - -std::wstring Line::GetName() const -{ - return m_name; -} - -void Line::Add(Artifact artifact) -{ - m_artifacts.emplace_back(artifact); -} - -std::vector Line::GetArtifacts() const -{ - return m_artifacts; -} - -LONG CTimelineView::GetTrackPos32(int nBar) -{ - SCROLLINFO si = {sizeof(si), SIF_TRACKPOS}; - GetScrollInfo(nBar, &si); - return si.nTrackPos; -} - -void CTimelineView::OnMouseMove(UINT /*nFlags*/, CPoint point) -{ - m_cursorPosition = std::max(int(point.x), gdi::s_leftTextAreaBorder); - SetFocus(); - Invalidate(); -} - -BOOL CTimelineView::OnMouseWheel(UINT /*nFlags*/, short zDelta, CPoint /*pt*/) -{ - m_mouseScrollCallback(m_cursorPosition - gdi::s_leftTextAreaBorder, m_selectedPosition - gdi::s_leftTextAreaBorder, zDelta); - Invalidate(); - return TRUE; -} - -void CTimelineView::OnLButtonDown(UINT /*flags*/, CPoint point) -{ - m_selectedPosition = std::max(int(point.x), gdi::s_leftTextAreaBorder); - SetFocus(); - Invalidate(); -} - -void CTimelineView::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar /*pScrollBar*/) -{ - if (nSBCode == SB_THUMBTRACK) - { - cdbg << "TRACK OnHScroll, nPos: " << nPos << "\n"; // received range is 1-100, unaffected by SetScrollRange ? - SetScrollPos(SB_HORZ, nPos); - Invalidate(); - } -} - -BOOL CTimelineView::OnInitDialog(CWindow /*wndFocus*/, LPARAM /*lInitParam*/) -{ - // SetScrollRange(SB_HORZ, 1, 500); // no effect?? - return 1; -} - -void CTimelineView::DoPaint(CDCHandle cdc) -{ - using namespace fusion; - gdi::TimelineDC dc(cdc); - auto backgroundArea = dc.GetClientArea(); - dc.FillSolidRect(&backgroundArea, RGB(255, 255, 255)); - - m_timelines = Recalculate(dc); - - PaintScale(dc); - PaintTimelines(dc); - PaintCursors(dc); -} - -void CTimelineView::SetFormatter(FormatFunction f) -{ - m_formatFunction = f; -} - -void CTimelineView::SetDataProvider(DataProvider f) -{ - m_dataProvider = f; -} - -void CTimelineView::SetMouseScrollCallback(MouseScrollCallback f) -{ - m_mouseScrollCallback = f; -} - -TimeLines CTimelineView::Recalculate(gdi::TimelineDC& /*dc*/) -{ - return m_dataProvider(); -} - -static const Pixel scaleBottom = 25; -static const Pixel topTimelinePos = 50; -static const Pixel minorTickHeight = 3; -static const Pixel majorTickHeight = 7; - -void CTimelineView::PaintScale(gdi::TimelineDC& dc) -{ - Pixel width = dc.GetClientArea().right - gdi::s_leftTextAreaBorder; - int y = scaleBottom; - auto x = gdi::s_leftTextAreaBorder + m_tickOffset; - int lastX = 0; - - // auto pos = m_tickOffset; - int majorTicks = width / m_minorTicksPerMajorTick; - for (int i = 0; i < majorTicks; ++i) - { - std::wstring s = m_formatFunction(x - gdi::s_leftTextAreaBorder); - dc.DrawTextOut(s, x - 15, 0); - dc.MoveTo(x, y); - dc.LineTo(x, y - majorTickHeight); - lastX = x; - x += (m_minorTicksPerMajorTick * m_minorTickSize); - } - - x = gdi::s_leftTextAreaBorder; - // int minorTicks = width / m_minorTickSize; - for (; x < lastX;) - { - dc.MoveTo(x, y); - dc.LineTo(x, y - minorTickHeight); - x += m_minorTickSize; - } -} - -void CTimelineView::PaintCursors(gdi::TimelineDC& dc) -{ - auto rect = dc.GetClientArea(); - - CPen redpen(CreatePen(PS_SOLID, 1, RGB(255, 0, 0))); - CPen greenpen(CreatePen(PS_SOLID, 1, RGB(0, 255, 0))); - - dc.SelectPen(redpen); - dc.MoveTo(m_cursorPosition, scaleBottom); - dc.LineTo(m_cursorPosition, rect.bottom); - - dc.SelectPen(greenpen); - dc.MoveTo(m_selectedPosition, scaleBottom); - dc.LineTo(m_selectedPosition, rect.bottom); -} - -void CTimelineView::PaintTimelines(gdi::TimelineDC& dc) -{ - auto rect = dc.GetClientArea(); - - int y = topTimelinePos; - y += GetTrackPos32(SB_HORZ); // test - for (const auto& line : m_timelines) - { - auto grey = RGB(160, 160, 170); - dc.DrawTimeline(line->GetName(), 0, y, rect.right, grey); - for (auto& artifact : line->GetArtifacts()) - { - // switch (artifact. - dc.DrawSolidFlag(L"tag", artifact.GetPosition() + gdi::s_leftTextAreaBorder, y, artifact.GetColor(), artifact.GetFillColor()); - } - y += 25; - } -} - - -} // namespace gdi -} // namespace fusion +// (C) Copyright Gert-Jan de Vos and Jan Wilmans 2016. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#include "DebugViewppLib/CTimelineView.h" +#include +#include +#include +#include "CobaltFusion/dbgstream.h" +#include "CobaltFusion/Str.h" +#include "CobaltFusion/stringbuilder.h" +#include + +namespace fusion { +namespace gdi { + +Artifact::Artifact(Pixel position, Artifact::Type type) : + m_position(position), + m_type(type), + m_color(RGB(0, 0, 0)), + m_fillcolor(RGB(255, 255, 255)) +{ +} + +Artifact::Artifact(Pixel position, Artifact::Type type, COLORREF color) : + m_position(position), + m_type(type), + m_color(color), + m_fillcolor(RGB(255, 255, 255)) +{ +} + +Artifact::Artifact(Pixel position, Artifact::Type type, COLORREF color, COLORREF fillcolor) : + m_position(position), + m_type(type), + m_color(color), + m_fillcolor(fillcolor) +{ +} + +void Artifact::SetColor(COLORREF color) +{ + m_color = color; +} + +void Artifact::SetFillColor(COLORREF color) +{ + m_fillcolor = color; +} + +COLORREF Artifact::GetColor() const +{ + return m_color; +} + +COLORREF Artifact::GetFillColor() const +{ + return m_fillcolor; +} + +Pixel Artifact::GetPosition() const +{ + return m_position; +} + +Line::Line(const std::wstring& name) : + m_name(name) +{ +} + +std::wstring Line::GetName() const +{ + return m_name; +} + +void Line::Add(Artifact artifact) +{ + m_artifacts.emplace_back(artifact); +} + +std::vector Line::GetArtifacts() const +{ + return m_artifacts; +} + +LONG CTimelineView::GetTrackPos32(int nBar) +{ + SCROLLINFO si = {sizeof(si), SIF_TRACKPOS}; + GetScrollInfo(nBar, &si); + return si.nTrackPos; +} + +void CTimelineView::OnMouseMove(UINT /*nFlags*/, CPoint point) +{ + m_cursorPosition = std::max(int(point.x), gdi::s_leftTextAreaBorder); + SetFocus(); + Invalidate(); +} + +BOOL CTimelineView::OnMouseWheel(UINT /*nFlags*/, short zDelta, CPoint /*pt*/) +{ + m_mouseScrollCallback(m_cursorPosition - gdi::s_leftTextAreaBorder, m_selectedPosition - gdi::s_leftTextAreaBorder, zDelta); + Invalidate(); + return TRUE; +} + +void CTimelineView::OnLButtonDown(UINT /*flags*/, CPoint point) +{ + m_selectedPosition = std::max(int(point.x), gdi::s_leftTextAreaBorder); + SetFocus(); + Invalidate(); +} + +void CTimelineView::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar /*pScrollBar*/) +{ + if (nSBCode == SB_THUMBTRACK) + { + cdbg << "TRACK OnHScroll, nPos: " << nPos << "\n"; // received range is 1-100, unaffected by SetScrollRange ? + SetScrollPos(SB_HORZ, nPos); + Invalidate(); + } +} + +BOOL CTimelineView::OnInitDialog(CWindow /*wndFocus*/, LPARAM /*lInitParam*/) +{ + // SetScrollRange(SB_HORZ, 1, 500); // no effect?? + return 1; +} + +void CTimelineView::DoPaint(CDCHandle cdc) +{ + using namespace fusion; + gdi::TimelineDC dc(cdc); + auto backgroundArea = dc.GetClientArea(); + dc.FillSolidRect(&backgroundArea, RGB(255, 255, 255)); + + m_timelines = Recalculate(dc); + + PaintScale(dc); + PaintTimelines(dc); + PaintCursors(dc); +} + +void CTimelineView::SetFormatter(FormatFunction f) +{ + m_formatFunction = f; +} + +void CTimelineView::SetDataProvider(DataProvider f) +{ + m_dataProvider = f; +} + +void CTimelineView::SetMouseScrollCallback(MouseScrollCallback f) +{ + m_mouseScrollCallback = f; +} + +TimeLines CTimelineView::Recalculate(gdi::TimelineDC& /*dc*/) +{ + return m_dataProvider(); +} + +static const Pixel scaleBottom = 25; +static const Pixel topTimelinePos = 50; +static const Pixel minorTickHeight = 3; +static const Pixel majorTickHeight = 7; + +void CTimelineView::PaintScale(gdi::TimelineDC& dc) +{ + Pixel width = dc.GetClientArea().right - gdi::s_leftTextAreaBorder; + int y = scaleBottom; + auto x = gdi::s_leftTextAreaBorder + m_tickOffset; + int lastX = 0; + + // auto pos = m_tickOffset; + int majorTicks = width / m_minorTicksPerMajorTick; + for (int i = 0; i < majorTicks; ++i) + { + std::wstring s = m_formatFunction(x - gdi::s_leftTextAreaBorder); + dc.DrawTextOut(s, x - 15, 0); + dc.MoveTo(x, y); + dc.LineTo(x, y - majorTickHeight); + lastX = x; + x += (m_minorTicksPerMajorTick * m_minorTickSize); + } + + x = gdi::s_leftTextAreaBorder; + // int minorTicks = width / m_minorTickSize; + for (; x < lastX;) + { + dc.MoveTo(x, y); + dc.LineTo(x, y - minorTickHeight); + x += m_minorTickSize; + } +} + +void CTimelineView::PaintCursors(gdi::TimelineDC& dc) +{ + auto rect = dc.GetClientArea(); + + CPen redpen(CreatePen(PS_SOLID, 1, RGB(255, 0, 0))); + CPen greenpen(CreatePen(PS_SOLID, 1, RGB(0, 255, 0))); + + dc.SelectPen(redpen); + dc.MoveTo(m_cursorPosition, scaleBottom); + dc.LineTo(m_cursorPosition, rect.bottom); + + dc.SelectPen(greenpen); + dc.MoveTo(m_selectedPosition, scaleBottom); + dc.LineTo(m_selectedPosition, rect.bottom); +} + +void CTimelineView::PaintTimelines(gdi::TimelineDC& dc) +{ + auto rect = dc.GetClientArea(); + + int y = topTimelinePos; + y += GetTrackPos32(SB_HORZ); // test + for (const auto& line : m_timelines) + { + auto grey = RGB(160, 160, 170); + dc.DrawTimeline(line->GetName(), 0, y, rect.right, grey); + for (auto& artifact : line->GetArtifacts()) + { + // switch (artifact. + dc.DrawSolidFlag(L"tag", artifact.GetPosition() + gdi::s_leftTextAreaBorder, y, artifact.GetColor(), artifact.GetFillColor()); + } + y += 25; + } +} + + +} // namespace gdi +} // namespace fusion diff --git a/application/DebugViewppLib/Conversions.cpp b/application/DebugViewppLib/Conversions.cpp index a98f0b29..ca1d9cdc 100644 --- a/application/DebugViewppLib/Conversions.cpp +++ b/application/DebugViewppLib/Conversions.cpp @@ -1,170 +1,170 @@ -// (C) Copyright Gert-Jan de Vos and Jan Wilmans 2013. -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE_1_0.txt or copy at -// http://www.boost.org/LICENSE_1_0.txt) - -#include -#include -#include "Win32/Utilities.h" -#include "Win32/Win32Lib.h" -#include "CobaltFusion/stringbuilder.h" -#include "DebugViewppLib/Conversions.h" - -namespace fusion { -namespace debugviewpp { - -std::string GetTimeText(double time) -{ - return stringbuilder() << std::fixed << std::setprecision(6) << time; -} - -std::string GetDateText(const SYSTEMTIME& st) -{ - int size = GetDateFormatA(LOCALE_USER_DEFAULT, 0, &st, nullptr, nullptr, 0); - std::vector buf(size); - GetDateFormatA(LOCALE_USER_DEFAULT, 0, &st, nullptr, buf.data(), size); - return std::string(buf.data(), size - 1); -} - -std::string GetDateText(const FILETIME& ft) -{ - return GetDateText(Win32::FileTimeToSystemTime(Win32::FileTimeToLocalFileTime(ft))); -} - -std::string GetTimeText(const SYSTEMTIME& st) -{ - char buf[32]; - sprintf_s(buf, "%02d:%02d:%02d.%03d", st.wHour, st.wMinute, st.wSecond, st.wMilliseconds); - return buf; -} - -std::string GetDateTimeText(const FILETIME& filetime) -{ - // convert the FILETIME from UTC to local timezone so the time so written as 'clocktime' - auto st = Win32::FileTimeToSystemTime(Win32::FileTimeToLocalFileTime(filetime)); - char buf[64]; - sprintf_s(buf, "%04d/%02d/%02d %02d:%02d:%02d.%03d", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds); - return buf; -} - -std::string GetTimeText(const FILETIME& ft) -{ - if (ft.dwHighDateTime == 0 && ft.dwLowDateTime == 0) - { - return "0"; // prevent endlessly repeating exception messageboxes when reading a corrupted file - } - return GetTimeText(Win32::FileTimeToSystemTime(Win32::FileTimeToLocalFileTime(ft))); -} - -SYSTEMTIME GetSystemTime(WORD year, WORD month, WORD day) -{ - SYSTEMTIME st = {}; - st.wYear = year; - st.wMonth = month; - st.wDay = day; - return st; -} - -USTimeConverter::USTimeConverter() : - m_lastFileTime(), - m_firstValue(true) -{ -} - -FILETIME USTimeConverter::USTimeToFiletime(WORD h, WORD m, WORD s, WORD /*ms*/) -{ - if (m_firstValue) - { - m_firstValue = false; - auto st = GetSystemTime(2000, 1, 1); - st.wHour = h; - st.wMinute = m; - st.wSecond = s; - st.wMilliseconds = 0; // todo: why is 'ms' not used? - m_lastFileTime = Win32::SystemTimeToFileTime(st); - } - - auto st = Win32::FileTimeToSystemTime(m_lastFileTime); - st.wHour = h; - st.wMinute = m; - st.wSecond = s; - st.wMilliseconds = 0; - auto ft = Win32::SystemTimeToFileTime(st); - - if (ft < m_lastFileTime) // is ft before m_lastFileTime, then assume it is on the next day. - { - st.wDay += 1; - } - m_lastFileTime = Win32::SystemTimeToFileTime(st); - return Win32::LocalFileTimeToFileTime(ft); // convert to UTC -} - -// read localtime in format "hh:mm:ss tt" (AM/PM postfix) -bool USTimeConverter::ReadLocalTimeUSRegion(const std::string& text, FILETIME& ft) -{ - std::istringstream is(text); - WORD h = 0; - WORD m = 0; - WORD s = 0; - char c1 = 0; - char c2 = 0; - char p1 = 0; - char p2 = 0; - is >> h >> c1 >> m >> c2 >> s; - - if (!(is && c1 == ':' && c2 == ':')) - { - return false; - } - if (h == 12) - { - h = 0; - } - - is >> p1 >> p2; - if (is && p1 == 'P' && p2 == 'M') - { - h += 12; - } - - ft = USTimeToFiletime(h, m, s, 0); - return true; -} - -// read localtime in format "hh:mm:ss.ms tt" (AM/PM postfix) -bool USTimeConverter::ReadLocalTimeUSRegionMs(const std::string& text, FILETIME& ft) -{ - std::istringstream is(text); - - WORD h = 0; - WORD m = 0; - WORD s = 0; - WORD ms = 0; - char c1 = 0; - char c2 = 0; - char p1 = 0; - char p2 = 0; - char d1 = 0; - - is >> h >> c1 >> m >> c2 >> s >> d1 >> ms; - if (!(is && c1 == ':' && c2 == ':' && d1 == '.')) - { - return false; - } - if (h == 12) - { - h = 0; - } - - is >> p1 >> p2; - if (is && p1 == 'P' && p2 == 'M') - { - h += 12; - } - - ft = USTimeToFiletime(h, m, s, ms); - return true; -} - -} // namespace debugviewpp -} // namespace fusion +// (C) Copyright Gert-Jan de Vos and Jan Wilmans 2013. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#include +#include +#include "Win32/Utilities.h" +#include "Win32/Win32Lib.h" +#include "CobaltFusion/stringbuilder.h" +#include "DebugViewppLib/Conversions.h" + +namespace fusion { +namespace debugviewpp { + +std::string GetTimeText(double time) +{ + return stringbuilder() << std::fixed << std::setprecision(6) << time; +} + +std::string GetDateText(const SYSTEMTIME& st) +{ + int size = GetDateFormatA(LOCALE_USER_DEFAULT, 0, &st, nullptr, nullptr, 0); + std::vector buf(size); + GetDateFormatA(LOCALE_USER_DEFAULT, 0, &st, nullptr, buf.data(), size); + return std::string(buf.data(), size - 1); +} + +std::string GetDateText(const FILETIME& ft) +{ + return GetDateText(Win32::FileTimeToSystemTime(Win32::FileTimeToLocalFileTime(ft))); +} + +std::string GetTimeText(const SYSTEMTIME& st) +{ + char buf[32]; + sprintf_s(buf, "%02d:%02d:%02d.%03d", st.wHour, st.wMinute, st.wSecond, st.wMilliseconds); + return buf; +} + +std::string GetDateTimeText(const FILETIME& filetime) +{ + // convert the FILETIME from UTC to local timezone so the time so written as 'clocktime' + auto st = Win32::FileTimeToSystemTime(Win32::FileTimeToLocalFileTime(filetime)); + char buf[64]; + sprintf_s(buf, "%04d/%02d/%02d %02d:%02d:%02d.%03d", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds); + return buf; +} + +std::string GetTimeText(const FILETIME& ft) +{ + if (ft.dwHighDateTime == 0 && ft.dwLowDateTime == 0) + { + return "0"; // prevent endlessly repeating exception messageboxes when reading a corrupted file + } + return GetTimeText(Win32::FileTimeToSystemTime(Win32::FileTimeToLocalFileTime(ft))); +} + +SYSTEMTIME GetSystemTime(WORD year, WORD month, WORD day) +{ + SYSTEMTIME st = {}; + st.wYear = year; + st.wMonth = month; + st.wDay = day; + return st; +} + +USTimeConverter::USTimeConverter() : + m_lastFileTime(), + m_firstValue(true) +{ +} + +FILETIME USTimeConverter::USTimeToFiletime(WORD h, WORD m, WORD s, WORD /*ms*/) +{ + if (m_firstValue) + { + m_firstValue = false; + auto st = GetSystemTime(2000, 1, 1); + st.wHour = h; + st.wMinute = m; + st.wSecond = s; + st.wMilliseconds = 0; // todo: why is 'ms' not used? + m_lastFileTime = Win32::SystemTimeToFileTime(st); + } + + auto st = Win32::FileTimeToSystemTime(m_lastFileTime); + st.wHour = h; + st.wMinute = m; + st.wSecond = s; + st.wMilliseconds = 0; + auto ft = Win32::SystemTimeToFileTime(st); + + if (ft < m_lastFileTime) // is ft before m_lastFileTime, then assume it is on the next day. + { + st.wDay += 1; + } + m_lastFileTime = Win32::SystemTimeToFileTime(st); + return Win32::LocalFileTimeToFileTime(ft); // convert to UTC +} + +// read localtime in format "hh:mm:ss tt" (AM/PM postfix) +bool USTimeConverter::ReadLocalTimeUSRegion(const std::string& text, FILETIME& ft) +{ + std::istringstream is(text); + WORD h = 0; + WORD m = 0; + WORD s = 0; + char c1 = 0; + char c2 = 0; + char p1 = 0; + char p2 = 0; + is >> h >> c1 >> m >> c2 >> s; + + if (!(is && c1 == ':' && c2 == ':')) + { + return false; + } + if (h == 12) + { + h = 0; + } + + is >> p1 >> p2; + if (is && p1 == 'P' && p2 == 'M') + { + h += 12; + } + + ft = USTimeToFiletime(h, m, s, 0); + return true; +} + +// read localtime in format "hh:mm:ss.ms tt" (AM/PM postfix) +bool USTimeConverter::ReadLocalTimeUSRegionMs(const std::string& text, FILETIME& ft) +{ + std::istringstream is(text); + + WORD h = 0; + WORD m = 0; + WORD s = 0; + WORD ms = 0; + char c1 = 0; + char c2 = 0; + char p1 = 0; + char p2 = 0; + char d1 = 0; + + is >> h >> c1 >> m >> c2 >> s >> d1 >> ms; + if (!(is && c1 == ':' && c2 == ':' && d1 == '.')) + { + return false; + } + if (h == 12) + { + h = 0; + } + + is >> p1 >> p2; + if (is && p1 == 'P' && p2 == 'M') + { + h += 12; + } + + ft = USTimeToFiletime(h, m, s, ms); + return true; +} + +} // namespace debugviewpp +} // namespace fusion diff --git a/application/DebugViewppLib/DBWinReader.cpp b/application/DebugViewppLib/DBWinReader.cpp index ff9536e0..008b44b9 100644 --- a/application/DebugViewppLib/DBWinReader.cpp +++ b/application/DebugViewppLib/DBWinReader.cpp @@ -42,17 +42,10 @@ DBWinReader::DBWinReader(Timer& timer, ILineBuffer& linebuffer, bool global) : { SetDescription(global ? L"Global Win32 Messages" : L"Win32 Messages"); - //Option 1: - //Win32::AdjustObjectDACL(m_hBuffer.get()); - //Win32::AdjustObjectDACL(m_dbWinBufferReady.get()); - //Win32::AdjustObjectDACL(m_dbWinDataReady.get()); - - //Option 2: - Win32::DeleteObjectDACL(m_hBuffer.get()); - Win32::DeleteObjectDACL(m_dbWinBufferReady.get()); - Win32::DeleteObjectDACL(m_dbWinDataReady.get()); - - // TODO(jan): Please test this and choose one + // required permissions for messages from UWP applications + Win32::AdjustObjectDACL(m_hBuffer.get()); + Win32::AdjustObjectDACL(m_dbWinBufferReady.get()); + Win32::AdjustObjectDACL(m_dbWinDataReady.get()); Win32::SetEvent(m_dbWinBufferReady); } diff --git a/application/DebugViewppLib/DbgviewReader.cpp b/application/DebugViewppLib/DbgviewReader.cpp index 29556ae1..46b382ec 100644 --- a/application/DebugViewppLib/DbgviewReader.cpp +++ b/application/DebugViewppLib/DbgviewReader.cpp @@ -1,222 +1,222 @@ -// (C) Copyright Gert-Jan de Vos and Jan Wilmans 2013. -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE_1_0.txt or copy at -// http://www.boost.org/LICENSE_1_0.txt) - -#include "Win32/Win32Lib.h" -#include "CobaltFusion/stringbuilder.h" -#include "DebugViewppLib/PolledLogSource.h" -#include "DebugViewppLib/DbgviewReader.h" -#include "DebugViewppLib/LineBuffer.h" - -#include -#include -#include - -namespace fusion { -namespace debugviewpp { - -const std::string SysinternalsDebugViewAgentPort = "2020"; - -DbgviewReader::DbgviewReader(Timer& timer, ILineBuffer& linebuffer, const std::string& hostname) : - PolledLogSource(timer, SourceType::Pipe, linebuffer, 40), - m_hostname(hostname), - m_thread([this] { Loop(); }) -{ - SetDescription(wstringbuilder() << "Dbgview Agent at " << m_hostname); -} - -std::vector Read(std::stringstream& is, size_t amount) -{ - if (amount < 1) - { - return std::vector(); - } - std::vector buffer(amount); - is.read(reinterpret_cast(buffer.data()), amount); - return buffer; -} - -namespace Magic { -const DWORD ColumnnOneMark = 1; -const DWORD ColumnnTwoMark = 2; -const DWORD Base = 0x83050000; -const DWORD CaptureKernelEnable = Base + 0x00; // 0 -const DWORD CaptureKernelDisable = Base + 0x04; // 1 -const DWORD VerboseKernelMessagesEnable = Base + 0x08; // 2 // Meaning of these 'VerboseKernel' values was never confirmed -const DWORD VerboseKernelMessagesDisable = Base + 0x0C; // 3 // -const DWORD PassThroughDisable = Base + 0x10; // 4 -const DWORD PassThroughEnable = Base + 0x14; // 5 -const DWORD CaptureWin32Enable = Base + 0x18; // 6 -const DWORD CaptureWin32Disable = Base + 0x1c; // 7 -const DWORD Unknown3 = Base + 0x20; // 8 -const DWORD RequestUnknown = Base + 0x24; // 9 // answer: 0x7fffffff -const DWORD RequestQueryPerformanceFrequency = Base + 0x28; // A -const DWORD Unknown4 = Base + 0x2C; -const DWORD Unknown5 = Base + 0x30; -const DWORD ForceCarriageReturnsEnable = Base + 0x34; -const DWORD ForceCarriageReturnsDisable = Base + 0x38; -} // namespace Magic - -template -std::string ToHex(const T& s) -{ - std::ostringstream result; - result << "[" << s.size() << "] "; - - for (size_t i = 0; i < s.size(); ++i) - result << std::hex << std::setfill('0') << std::setw(2) << std::uppercase << (static_cast(s[i]) & 0xff) << " "; - return result.str(); -} - -template -std::string ToChar(const T& s) -{ - std::ostringstream result; - result << "[" << s.size() << "] "; - - for (size_t i = 0; i < s.size(); ++i) - { - if (s[i] > 32) - { - result << std::setfill(' ') << std::setw(2) << static_cast(s[i]) << " "; - } - else - { - result << " . "; - } - } - return result.str(); -} - -void DbgviewReader::SetAutoNewLine(bool value) -{ - LogSource::SetAutoNewLine(value); - // todo: send ForceCarriageReturnsEnable/ForceCarriageReturnsDisable to dbgview-agent -} - -void DbgviewReader::Loop() -{ - SetDescription(wstringbuilder() << "Dbgview Agent at " << m_hostname); - m_iostream.connect(m_hostname, SysinternalsDebugViewAgentPort); - const std::string processName("[tcp]"); - - Write(m_iostream, Magic::RequestQueryPerformanceFrequency); - auto qpFrequency = Read(m_iostream); - long long t0 = 0; - bool first = true; - - if (!m_iostream || qpFrequency == 0) - { - LogSource::Add(stringbuilder() << "Unable to connect to " << GetDescription() << ", " << m_iostream.error().message()); - Signal(); - return; - } - - Write(m_iostream, Magic::CaptureKernelEnable); - Write(m_iostream, Magic::VerboseKernelMessagesEnable); - Write(m_iostream, Magic::CaptureWin32Enable); - Write(m_iostream, Magic::PassThroughEnable); - - if (GetAutoNewLine()) - { - Write(m_iostream, Magic::ForceCarriageReturnsEnable); - } - else - { - Write(m_iostream, Magic::ForceCarriageReturnsDisable); - } - - double timerUnit = 1. / qpFrequency; - AddMessage(stringbuilder() << "Connected to " << GetDescription()); - Signal(); - - while (!AtEnd()) - { - m_iostream.clear(); - auto messageLength = Read(m_iostream); - - if (m_iostream.eof()) - { - AddMessage(stringbuilder() << "Connection to " << GetDescription() << " closed."); - LogSource::Abort(); - Signal(); - break; - } - - if (!m_iostream || messageLength >= 0x7fffffff) - { - AddMessage(0, processName, ""); - Signal(); - break; - } - - if (messageLength == 0) - { // keep alive - continue; - } - - // dont read from the tcp::iostream directly, - // instead use read() to receive the complete message. - // this allows us to use ss.tellg() to determine the amount of trash bytes. - std::vector buffer(messageLength); - m_iostream.read(buffer.data(), messageLength); - std::stringstream ss(std::ios_base::in | std::ios_base::out | std::ios::binary); - ss.write(buffer.data(), buffer.size()); - - DWORD pid = 0; - std::string msg; - for (;;) - { - Read(ss); // lineNr - if (!ss) - { - break; - } - - auto filetime = Read(ss); - auto qpcTime = Read(ss); - if (first) - { - t0 = qpcTime; - first = false; - } - auto time = (qpcTime - t0) * timerUnit; - - if (buffer[20] == Magic::ColumnnOneMark) - { - unsigned char c1 = 0; - unsigned char c2 = 0; - if (!((ss >> c1 >> pid >> c2) && c1 == Magic::ColumnnOneMark && c2 == Magic::ColumnnTwoMark)) - { - AddMessage(0, processName, ""); - break; - } - Read(ss, 1); // discard one leading space - } - - std::getline(ss, msg, '\0'); - msg.push_back('\n'); // newlines are never send as part of the message - AddMessage(time, filetime, pid, processName, msg); - - // strangely, messages are always send in multiples of 4 bytes. - // this means depending on the message length there are 1, 2 or 3 trailing bytes of undefined data. - auto remainder = static_cast(ss.tellg() % 4); - if (remainder > 0) - { - Read(ss, 4 - remainder); - } - } - Signal(); - } -} - -void DbgviewReader::Abort() -{ - m_iostream.close(); - LogSource::Abort(); - m_thread.join(); -} - -} // namespace debugviewpp -} // namespace fusion +// (C) Copyright Gert-Jan de Vos and Jan Wilmans 2013. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#include "Win32/Win32Lib.h" +#include "CobaltFusion/stringbuilder.h" +#include "DebugViewppLib/PolledLogSource.h" +#include "DebugViewppLib/DbgviewReader.h" +#include "DebugViewppLib/LineBuffer.h" + +#include +#include +#include + +namespace fusion { +namespace debugviewpp { + +const std::string SysinternalsDebugViewAgentPort = "2020"; + +DbgviewReader::DbgviewReader(Timer& timer, ILineBuffer& linebuffer, const std::string& hostname) : + PolledLogSource(timer, SourceType::Pipe, linebuffer, 40), + m_hostname(hostname), + m_thread([this] { Loop(); }) +{ + SetDescription(wstringbuilder() << "Dbgview Agent at " << m_hostname); +} + +std::vector Read(std::stringstream& is, size_t amount) +{ + if (amount < 1) + { + return std::vector(); + } + std::vector buffer(amount); + is.read(reinterpret_cast(buffer.data()), amount); + return buffer; +} + +namespace Magic { +const DWORD ColumnnOneMark = 1; +const DWORD ColumnnTwoMark = 2; +const DWORD Base = 0x83050000; +const DWORD CaptureKernelEnable = Base + 0x00; // 0 +const DWORD CaptureKernelDisable = Base + 0x04; // 1 +const DWORD VerboseKernelMessagesEnable = Base + 0x08; // 2 // Meaning of these 'VerboseKernel' values was never confirmed +const DWORD VerboseKernelMessagesDisable = Base + 0x0C; // 3 // +const DWORD PassThroughDisable = Base + 0x10; // 4 +const DWORD PassThroughEnable = Base + 0x14; // 5 +const DWORD CaptureWin32Enable = Base + 0x18; // 6 +const DWORD CaptureWin32Disable = Base + 0x1c; // 7 +const DWORD Unknown3 = Base + 0x20; // 8 +const DWORD RequestUnknown = Base + 0x24; // 9 // answer: 0x7fffffff +const DWORD RequestQueryPerformanceFrequency = Base + 0x28; // A +const DWORD Unknown4 = Base + 0x2C; +const DWORD Unknown5 = Base + 0x30; +const DWORD ForceCarriageReturnsEnable = Base + 0x34; +const DWORD ForceCarriageReturnsDisable = Base + 0x38; +} // namespace Magic + +template +std::string ToHex(const T& s) +{ + std::ostringstream result; + result << "[" << s.size() << "] "; + + for (size_t i = 0; i < s.size(); ++i) + result << std::hex << std::setfill('0') << std::setw(2) << std::uppercase << (static_cast(s[i]) & 0xff) << " "; + return result.str(); +} + +template +std::string ToChar(const T& s) +{ + std::ostringstream result; + result << "[" << s.size() << "] "; + + for (size_t i = 0; i < s.size(); ++i) + { + if (s[i] > 32) + { + result << std::setfill(' ') << std::setw(2) << static_cast(s[i]) << " "; + } + else + { + result << " . "; + } + } + return result.str(); +} + +void DbgviewReader::SetAutoNewLine(bool value) +{ + LogSource::SetAutoNewLine(value); + // todo: send ForceCarriageReturnsEnable/ForceCarriageReturnsDisable to dbgview-agent +} + +void DbgviewReader::Loop() +{ + SetDescription(wstringbuilder() << "Dbgview Agent at " << m_hostname); + m_iostream.connect(m_hostname, SysinternalsDebugViewAgentPort); + const std::string processName("[tcp]"); + + Write(m_iostream, Magic::RequestQueryPerformanceFrequency); + auto qpFrequency = Read(m_iostream); + long long t0 = 0; + bool first = true; + + if (!m_iostream || qpFrequency == 0) + { + LogSource::Add(stringbuilder() << "Unable to connect to " << GetDescription() << ", " << m_iostream.error().message()); + Signal(); + return; + } + + Write(m_iostream, Magic::CaptureKernelEnable); + Write(m_iostream, Magic::VerboseKernelMessagesEnable); + Write(m_iostream, Magic::CaptureWin32Enable); + Write(m_iostream, Magic::PassThroughEnable); + + if (GetAutoNewLine()) + { + Write(m_iostream, Magic::ForceCarriageReturnsEnable); + } + else + { + Write(m_iostream, Magic::ForceCarriageReturnsDisable); + } + + double timerUnit = 1. / qpFrequency; + AddMessage(stringbuilder() << "Connected to " << GetDescription()); + Signal(); + + while (!AtEnd()) + { + m_iostream.clear(); + auto messageLength = Read(m_iostream); + + if (m_iostream.eof()) + { + AddMessage(stringbuilder() << "Connection to " << GetDescription() << " closed."); + LogSource::Abort(); + Signal(); + break; + } + + if (!m_iostream || messageLength >= 0x7fffffff) + { + AddMessage(0, processName, ""); + Signal(); + break; + } + + if (messageLength == 0) + { // keep alive + continue; + } + + // dont read from the tcp::iostream directly, + // instead use read() to receive the complete message. + // this allows us to use ss.tellg() to determine the amount of trash bytes. + std::vector buffer(messageLength); + m_iostream.read(buffer.data(), messageLength); + std::stringstream ss(std::ios_base::in | std::ios_base::out | std::ios::binary); + ss.write(buffer.data(), buffer.size()); + + DWORD pid = 0; + std::string msg; + for (;;) + { + Read(ss); // lineNr + if (!ss) + { + break; + } + + auto filetime = Read(ss); + auto qpcTime = Read(ss); + if (first) + { + t0 = qpcTime; + first = false; + } + auto time = (qpcTime - t0) * timerUnit; + + if (buffer[20] == Magic::ColumnnOneMark) + { + unsigned char c1 = 0; + unsigned char c2 = 0; + if (!((ss >> c1 >> pid >> c2) && c1 == Magic::ColumnnOneMark && c2 == Magic::ColumnnTwoMark)) + { + AddMessage(0, processName, ""); + break; + } + Read(ss, 1); // discard one leading space + } + + std::getline(ss, msg, '\0'); + msg.push_back('\n'); // newlines are never send as part of the message + AddMessage(time, filetime, pid, processName, msg); + + // strangely, messages are always send in multiples of 4 bytes. + // this means depending on the message length there are 1, 2 or 3 trailing bytes of undefined data. + auto remainder = static_cast(ss.tellg() % 4); + if (remainder > 0) + { + Read(ss, 4 - remainder); + } + } + Signal(); + } +} + +void DbgviewReader::Abort() +{ + m_iostream.close(); + LogSource::Abort(); + m_thread.join(); +} + +} // namespace debugviewpp +} // namespace fusion diff --git a/application/DebugViewppLib/DebugView++Lib.h b/application/DebugViewppLib/DebugView++Lib.h index c79a089e..1c7637a3 100644 --- a/application/DebugViewppLib/DebugView++Lib.h +++ b/application/DebugViewppLib/DebugView++Lib.h @@ -1,6 +1,6 @@ // (C) Copyright Gert-Jan de Vos and Jan Wilmans 2013. // Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE_1_0.txt or copy at +// (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #pragma once @@ -18,5 +18,3 @@ #include "PipeReader.h" #include "Process.h" #include "ProcessReader.h" - -#pragma comment(lib, "DebugView++Lib.lib") diff --git a/application/DebugViewppLib/Debugview_kernel_client.cpp b/application/DebugViewppLib/Debugview_kernel_client.cpp new file mode 100644 index 00000000..814f1f6f --- /dev/null +++ b/application/DebugViewppLib/Debugview_kernel_client.cpp @@ -0,0 +1,148 @@ +// (C) Copyright Gert-Jan de Vos and Jan Wilmans 2013. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#include "DebugViewppLib/Debugview_kernel_client.h" + +constexpr const char* DRIVER_SERVICE_NAME = "debugviewdriver"; +constexpr const char* DRIVER_DISPLAY_NAME = "DbgView Kernel Message Driver"; + +bool FileExists(const std::string& path) +{ + DWORD fileAttributes = GetFileAttributesA(path.c_str()); + return (fileAttributes != INVALID_FILE_ATTRIBUTES && + !(fileAttributes & FILE_ATTRIBUTE_DIRECTORY)); +} + +bool StartDriverSvc() +{ + SC_HANDLE hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS); + if (!hSCManager) + return false; + + SC_HANDLE hService = OpenServiceA(hSCManager, DRIVER_SERVICE_NAME, SERVICE_START); + if (!hService) + { + CloseServiceHandle(hSCManager); + return false; + } + + if (!StartService(hService, 0, NULL)) + { + if (GetLastError() == ERROR_SERVICE_ALREADY_RUNNING) + { + std::cout << "Service is already running.\n"; + CloseServiceHandle(hService); + CloseServiceHandle(hSCManager); + return true; + } + std::cout << "Failed to start service. Error: " << GetLastError() << std::endl; + CloseServiceHandle(hService); + CloseServiceHandle(hSCManager); + return false; + } + + CloseServiceHandle(hService); + CloseServiceHandle(hSCManager); + return true; +} + +bool StopDriverSvc() +{ + SC_HANDLE hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS); + if (!hSCManager) + return false; + + SC_HANDLE hService = OpenServiceA(hSCManager, DRIVER_SERVICE_NAME, SERVICE_STOP); + if (!hService) + { + CloseServiceHandle(hSCManager); + return false; + } + + SERVICE_STATUS status; + if (!ControlService(hService, SERVICE_CONTROL_STOP, &status)) + { + DWORD err = GetLastError(); + if (err != ERROR_SERVICE_NOT_ACTIVE) + { + std::cout << "Failed to stop service. Error: " << err << std::endl; + } + } + + CloseServiceHandle(hService); + CloseServiceHandle(hSCManager); + return true; +} + +void InstallKernelMessagesDriver(const std::string& driverLocation) +{ + // try to uninstall first, in case the driver is somehow still loaded. + UninstallKernelMessagesDriver(); + + if (!FileExists(driverLocation)) + { + std::cout << "Driver file not found at: " << driverLocation << std::endl; + } + + SC_HANDLE hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS); + if (!hSCManager) + { + std::cout << "Failed to open Service Control Manager. Error: " << GetLastError() << std::endl; + } + + SC_HANDLE hService = CreateServiceA( + hSCManager, + DRIVER_SERVICE_NAME, + DRIVER_DISPLAY_NAME, + SERVICE_ALL_ACCESS, + SERVICE_KERNEL_DRIVER, + SERVICE_DEMAND_START, + SERVICE_ERROR_NORMAL, + driverLocation.c_str(), + NULL, NULL, NULL, NULL, NULL); + + if (!hService) + { + if (GetLastError() == ERROR_SERVICE_EXISTS) + { + std::cout << "Service already exists.\n"; + CloseServiceHandle(hSCManager); + } + std::cout << "Failed to create service. Error: " << GetLastError() << std::endl; + CloseServiceHandle(hSCManager); + } + CloseServiceHandle(hService); + CloseServiceHandle(hSCManager); + + StartDriverSvc(); +} + +void UninstallKernelMessagesDriver() +{ + StopDriverSvc(); + + SC_HANDLE hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS); + if (!hSCManager) + { + std::cout << "Failed to open Service Control Manager for uninstall. Error: " << GetLastError() << std::endl; + return; + } + + SC_HANDLE hService = OpenServiceA(hSCManager, DRIVER_SERVICE_NAME, DELETE); + if (!hService) + { + // this happens when the service is first loaded, because we always uninstall before installing + // std::cout << "Failed to open service for uninstall. Error: " << GetLastError() << std::endl; + CloseServiceHandle(hSCManager); + return; + } + + if (!DeleteService(hService)) + { + std::cout << "Failed to delete service. Error: " << GetLastError() << std::endl; + } + CloseServiceHandle(hService); + CloseServiceHandle(hSCManager); +} diff --git a/application/DebugViewppLib/FileReader.cpp b/application/DebugViewppLib/FileReader.cpp index 77673e15..45cec035 100644 --- a/application/DebugViewppLib/FileReader.cpp +++ b/application/DebugViewppLib/FileReader.cpp @@ -43,6 +43,11 @@ void FileReader::Abort() } } +bool FileReader::GetAutoNewLine() const +{ + return true; +} + void FileReader::PollThread() { // FILE_NOTIFY_CHANGE_SIZE is broken on windows vista and above in that it does not diff --git a/application/DebugViewppLib/KernelReader.cpp b/application/DebugViewppLib/KernelReader.cpp new file mode 100644 index 00000000..cfa23ca9 --- /dev/null +++ b/application/DebugViewppLib/KernelReader.cpp @@ -0,0 +1,157 @@ +// (C) Copyright Gert-Jan de Vos and Jan Wilmans 2013. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#include "CobaltFusion/Str.h" +#include "DebugViewppLib/PolledLogSource.h" +#include "DebugViewppLib/KernelReader.h" +#include "DebugViewppLib/LineBuffer.h" +#include "DebugViewppLib/Debugview_kernel_client.h" + +#include +#include +namespace fusion { +namespace debugviewpp { + +std::string GetDebugviewDriverLocation() +{ + const auto driver_name = "dbgvpp.sys"; + // this has issues, because there could already be an existing file + // in that case we would load a possibly different driver verion? + // an we also do not seem to be able to (always?) remove it ? + // windows something reports it is 'in-use' even through we unloaded it. + + std::filesystem::path driverLocation = driver_name; + const auto systemRoot = std::getenv("SystemRoot"); + if (systemRoot != nullptr) + { + driverLocation = std::format("{}\\System32\\driver\\{}", systemRoot, driver_name); + } + return driverLocation.string(); +} + +void KernelReader::StartListening() +{ + Win32::Handle handle(::CreateFile(strDbgviewKernelDriverDeviceName, + GENERIC_READ, + FILE_SHARE_READ, + NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + NULL)); + if (handle.get() == INVALID_HANDLE_VALUE) + { + printf("Could not find the kernel messages driver device name"); + throw std::runtime_error("Could not find the kernel messages driver device name"); + } + + // enable capture + DWORD dwOut = 0; + DWORD dwReturned = 0; + BOOL bRet = DeviceIoControl(handle.get(), DBGV_CAPTURE_KERNEL, NULL, 0, &dwOut, sizeof(dwOut), &dwReturned, NULL); + if (!bRet) + { + printf("DBGV_CAPTURE_KERNEL failed, err=%d\n", ::GetLastError()); + throw std::runtime_error("Could not enable capturing kernel messages"); + } + + m_handle = std::move(handle); + m_pBuf = reinterpret_cast(malloc(kernelMessageBufferSize)); +} + +void KernelReader::StopListening() +{ + BOOL bRet = DeviceIoControl(m_handle.get(), DBGV_UNCAPTURE_KERNEL, NULL, 0, NULL, 0, NULL, NULL); + if (!bRet) + { + printf("DBGV_UNCAPTURE_KERNEL failed, err=%d\n", ::GetLastError()); + } + m_handle.reset(); + ::free(m_pBuf); +} + +KernelReader::KernelReader(Timer& timer, ILineBuffer& linebuffer) : + PolledLogSource(timer, SourceType::Pipe, linebuffer, 1) +{ + SetDescription(L"Kernel Message Reader"); + InstallKernelMessagesDriver(GetDebugviewDriverLocation()); + Signal(); + StartListening(); + StartThread(); +} + +KernelReader::~KernelReader() +{ + StopListening(); + UninstallKernelMessagesDriver(); +} + +void KernelReader::Abort() +{ + AddMessage(0, "kernel", ""); + Signal(); + PolledLogSource::Abort(); +} + +bool KernelReader::AtEnd() const +{ + return false; +} + +void KernelReader::Poll() +{ + memset(m_pBuf, 0, kernelMessageBufferSize); + DWORD dwOut = 0; + ::DeviceIoControl(m_handle.get(), DBGV_READ_LOG, NULL, 0, m_pBuf, kernelMessageBufferSize, &dwOut, NULL); + if (dwOut == 0) + { + return; // no messages to be read + } + + PLOG_ITEM pNextItem = m_pBuf; + while (pNextItem->dwIndex != 0) + { + AddMessage(0, "kernel", pNextItem->strData); + pNextItem = (PLOG_ITEM)((char*)pNextItem + sizeof(LOG_ITEM) + (strlen(pNextItem->strData) + 4) / 4 * 4); + } +} + +void KernelReader::SetKernelMessagesDriverFeature(DWORD feature) +{ + DWORD dwOut = 0; + DWORD dwReturned = 0; + BOOL bRet = DeviceIoControl(m_handle.get(), feature, NULL, 0, &dwOut, sizeof(dwOut), &dwReturned, NULL); + if (!bRet) + { + printf("SetKernelMessagesDriverFeature for '%s' failed, err=%d\n", feature_to_string(feature).c_str(), ::GetLastError()); + return; + } +} + +void KernelReader::SetVerbose(bool value) +{ + if (value) + { + SetKernelMessagesDriverFeature(DBGV_SET_VERBOSE_MESSAGES); + } + else + { + SetKernelMessagesDriverFeature(DBGV_UNSET_VERBOSE_MESSAGES); + } +} + +void KernelReader::SetPassThrough(bool value) +{ + if (value) + { + SetKernelMessagesDriverFeature(DBGV_SET_PASSTHROUGH); + } + else + { + SetKernelMessagesDriverFeature(DBGV_UNSET_PASSTHROUGH); + } +} + +} // namespace debugviewpp +} // namespace fusion diff --git a/application/DebugViewppLib/LogSource.cpp b/application/DebugViewppLib/LogSource.cpp index 43880d6c..6641cfa2 100644 --- a/application/DebugViewppLib/LogSource.cpp +++ b/application/DebugViewppLib/LogSource.cpp @@ -92,5 +92,11 @@ void LogSource::AddInternal(const std::string& message) const m_linebuffer.Add(m_timer.Get(), Win32::GetSystemTimeAsFileTime(), 0, "[internal]", message, this); } +double LogSource::GetTimeSinceOrigin(long long ticks) const +{ + return m_timer.GetTimeSinceOrigin(ticks); +} + + } // namespace debugviewpp } // namespace fusion diff --git a/application/DebugViewppLib/LogSources.cpp b/application/DebugViewppLib/LogSources.cpp index cc9ee0c0..3fa0ea56 100644 --- a/application/DebugViewppLib/LogSources.cpp +++ b/application/DebugViewppLib/LogSources.cpp @@ -19,6 +19,7 @@ #include "DebugViewppLib/BinaryFileReader.h" #include "DebugViewppLib/AnyFileReader.h" #include "DebugViewppLib/DBWinReader.h" +#include "DebugViewppLib/KernelReader.h" #include "DebugViewppLib/DbgviewReader.h" #include "DebugViewppLib/SocketReader.h" #include "DebugViewppLib/TestSource.h" @@ -29,7 +30,7 @@ #include "DebugViewppLib/Loopback.h" // class Logsources has a vector and start a thread for LogSources::Listen() -// - Listen() exectues every LogSource::GetHandle() in m_sources and calls Notify() for any signaled handle. +// - Listen() executes every LogSource::GetHandle() in m_sources and calls Notify() for any signaled handle. // - LogSource::Notify reads input en writes to linebuffer (passed at construction) // @@ -430,6 +431,15 @@ DBWinReader* LogSources::AddDBWinReader(bool global) return pResult; } +KernelReader* LogSources::AddKernelReader() +{ + assert(m_executor.IsExecutorThread()); + auto kernelReader = std::make_unique(m_timer, m_linebuffer); + auto pResult = kernelReader.get(); + Add(std::move(kernelReader)); + return pResult; +} + TestSource* LogSources::AddTestSource() { assert(m_executor.IsExecutorThread()); diff --git a/application/DebugViewppLib/NewlineFilter.cpp b/application/DebugViewppLib/NewlineFilter.cpp index b26370b6..025ec79b 100644 --- a/application/DebugViewppLib/NewlineFilter.cpp +++ b/application/DebugViewppLib/NewlineFilter.cpp @@ -1,68 +1,68 @@ -// (C) Copyright Gert-Jan de Vos and Jan Wilmans 2013. -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE_1_0.txt or copy at -// http://www.boost.org/LICENSE_1_0.txt) - -#include "CobaltFusion/stringbuilder.h" -#include "DebugViewppLib/LogSource.h" -#include "DebugViewppLib/ProcessInfo.h" -#include "DebugViewppLib/NewlineFilter.h" -#include - -namespace fusion { -namespace debugviewpp { - -Lines NewlineFilter::Process(const Line& line) -{ - auto& message = m_lineBuffers[line.pid]; - message.reserve(512); - - Lines lines; - for (auto c : line.message) - { - if (c == '\r') - { - continue; - } - - if (c == '\n') - { - Line outputLine(line.time, line.systemTime, line.pid, line.processName, "", line.pLogSource); - std::swap(outputLine.message, message); - lines.emplace_back(std::move(outputLine)); - } - else - { - message.push_back(c); - } - } - - if (!message.empty()) - { - if (line.pLogSource->GetAutoNewLine() || message.size() > 8192) // 8k line limit prevents stack overflow in handling code - { - Line outputLine(line.time, line.systemTime, line.pid, line.processName, "", line.pLogSource); - std::swap(outputLine.message, message); - lines.emplace_back(std::move(outputLine)); - } - } - return lines; -} - -Lines NewlineFilter::FlushLinesFromTerminatedProcess(DWORD pid, HANDLE /*handle*/) // todo: why is handle unused? -{ - Lines lines; - if (m_lineBuffers.find(pid) != m_lineBuffers.end()) - { - if (!m_lineBuffers[pid].empty()) - { - // timestamp not filled, this will be done by the loopback source - lines.push_back(Line(0, FILETIME(), pid, "", m_lineBuffers[pid], nullptr)); - } - m_lineBuffers.erase(pid); - } - return lines; -} - -} // namespace debugviewpp -} // namespace fusion +// (C) Copyright Gert-Jan de Vos and Jan Wilmans 2013. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#include "CobaltFusion/stringbuilder.h" +#include "DebugViewppLib/LogSource.h" +#include "DebugViewppLib/ProcessInfo.h" +#include "DebugViewppLib/NewlineFilter.h" +#include + +namespace fusion { +namespace debugviewpp { + +Lines NewlineFilter::Process(const Line& line) +{ + auto& message = m_lineBuffers[line.pid]; + message.reserve(512); + + Lines lines; + for (auto c : line.message) + { + if (c == '\r') + { + continue; + } + + if (c == '\n') + { + Line outputLine(line.time, line.systemTime, line.pid, line.processName, "", line.pLogSource); + std::swap(outputLine.message, message); + lines.emplace_back(std::move(outputLine)); + } + else + { + message.push_back(c); + } + } + + if (!message.empty()) + { + if (line.pLogSource->GetAutoNewLine() || message.size() > 8192) // 8k line limit prevents stack overflow in handling code + { + Line outputLine(line.time, line.systemTime, line.pid, line.processName, "", line.pLogSource); + std::swap(outputLine.message, message); + lines.emplace_back(std::move(outputLine)); + } + } + return lines; +} + +Lines NewlineFilter::FlushLinesFromTerminatedProcess(DWORD pid, HANDLE /*handle*/) // todo: why is handle unused? +{ + Lines lines; + if (m_lineBuffers.find(pid) != m_lineBuffers.end()) + { + if (!m_lineBuffers[pid].empty()) + { + // timestamp not filled, this will be done by the loopback source + lines.push_back(Line(0, FILETIME(), pid, "", m_lineBuffers[pid], nullptr)); + } + m_lineBuffers.erase(pid); + } + return lines; +} + +} // namespace debugviewpp +} // namespace fusion diff --git a/application/DebugViewppLib/PolledLogSource.cpp b/application/DebugViewppLib/PolledLogSource.cpp index 1d3584dd..33e0d237 100644 --- a/application/DebugViewppLib/PolledLogSource.cpp +++ b/application/DebugViewppLib/PolledLogSource.cpp @@ -13,7 +13,7 @@ namespace debugviewpp { PollLine::PollLine(Win32::Handle handle, const std::string& message, const LogSource* pLogSource) : - timesValid(false), + timesAreValid(false), time(0.0), systemTime(FILETIME()), handle(std::move(handle)), @@ -24,7 +24,7 @@ PollLine::PollLine(Win32::Handle handle, const std::string& message, const LogSo } PollLine::PollLine(DWORD pid, const std::string& processName, const std::string& message, const LogSource* pLogSource) : - timesValid(false), + timesAreValid(false), time(0.0), systemTime(FILETIME()), pid(pid), @@ -35,7 +35,7 @@ PollLine::PollLine(DWORD pid, const std::string& processName, const std::string& } PollLine::PollLine(double time, FILETIME systemTime, DWORD pid, const std::string& processName, const std::string& message, const LogSource* pLogSource) : - timesValid(true), + timesAreValid(true), time(time), systemTime(systemTime), pid(pid), @@ -45,6 +45,7 @@ PollLine::PollLine(double time, FILETIME systemTime, DWORD pid, const std::strin { } +// pollFrequency is in Hz PolledLogSource::PolledLogSource(Timer& timer, SourceType::type sourceType, ILineBuffer& linebuffer, long pollFrequency) : LogSource(timer, sourceType, linebuffer), m_microsecondInterval(pollFrequency > 0 ? 1000000 / pollFrequency : 0), @@ -115,7 +116,7 @@ void PolledLogSource::Notify() { Add(line.handle.release(), line.message); } - else if (line.timesValid) + else if (line.timesAreValid) { Add(line.time, line.systemTime, line.pid, line.processName, line.message); } diff --git a/application/DebugViewppLib/SocketReader.cpp b/application/DebugViewppLib/SocketReader.cpp index 8b97e506..2df5a94c 100644 --- a/application/DebugViewppLib/SocketReader.cpp +++ b/application/DebugViewppLib/SocketReader.cpp @@ -1,94 +1,94 @@ -// (C) Copyright Gert-Jan de Vos and Jan Wilmans 2013. -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE_1_0.txt or copy at -// http://www.boost.org/LICENSE_1_0.txt) - -#include -#include "CobaltFusion/stringbuilder.h" -#include "DebugViewppLib/SocketReader.h" - -namespace fusion { -namespace debugviewpp { - -SocketReader::SocketReader(Timer& timer, ILineBuffer& lineBuffer, int port) : - LogSource(timer, SourceType::Udp, lineBuffer), - m_wsa(2, 2), - m_socket(Win32::WSASocket(AF_INET, SOCK_DGRAM, IPPROTO_UDP, nullptr, 0, WSA_FLAG_OVERLAPPED)), - m_event(Win32::CreateEvent(nullptr, false, true, nullptr)), - m_fromLen(0), - m_busy(false) -{ - SetDescription(wstringbuilder() << L"Listening at UDP port " << port); - - m_overlapped.hEvent = m_event.get(); - m_wsaBuf[0].len = static_cast(m_buffer.size()); - m_wsaBuf[0].buf = m_buffer.data(); - - sockaddr_in sa; - sa.sin_family = AF_INET; - sa.sin_addr.s_addr = htonl(INADDR_ANY); - sa.sin_port = htons(u_short(port)); - Win32::bind(m_socket, sa); -} - -HANDLE SocketReader::GetHandle() const -{ - return m_event.get(); -} - -std::string bufferToString(const char* buffer, int size) -{ - size_t length = strnlen(buffer, size); - return std::string(buffer, length); -} - -void SocketReader::Notify() -{ - if (m_busy) - { - int len = CompleteReceive(); - Add(0, GetProcessText(), bufferToString(m_buffer.data(), len)); - } - - for (;;) - { - int len = BeginReceive(); - if (len < 0) - { - return; - } - Add(0, GetProcessText(), bufferToString(m_buffer.data(), len)); - } -} - -int SocketReader::BeginReceive() -{ - assert(!m_busy); - DWORD count; - DWORD flags = 0; - m_fromLen = sizeof(m_from); - if (Win32::WSARecvFrom(m_socket, m_wsaBuf, 1, &count, &flags, m_from, m_fromLen, &m_overlapped, nullptr)) - { - return count; - } - - m_busy = true; - return -1; -} - -int SocketReader::CompleteReceive() -{ - assert(m_busy); - DWORD flags = 0; - auto count = Win32::WSAGetOverlappedResult(m_socket, m_overlapped, false, flags); - m_busy = false; - return count; -} - -std::string SocketReader::GetProcessText() const -{ - return stringbuilder() << "[UDP " << inet_ntoa(m_from.sin_addr) << ":" << ntohs(m_from.sin_port) << "]"; -} - -} // namespace debugviewpp -} // namespace fusion +// (C) Copyright Gert-Jan de Vos and Jan Wilmans 2013. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#include +#include "CobaltFusion/stringbuilder.h" +#include "DebugViewppLib/SocketReader.h" + +namespace fusion { +namespace debugviewpp { + +SocketReader::SocketReader(Timer& timer, ILineBuffer& lineBuffer, int port) : + LogSource(timer, SourceType::Udp, lineBuffer), + m_wsa(2, 2), + m_socket(Win32::WSASocket(AF_INET, SOCK_DGRAM, IPPROTO_UDP, nullptr, 0, WSA_FLAG_OVERLAPPED)), + m_event(Win32::CreateEvent(nullptr, false, true, nullptr)), + m_fromLen(0), + m_busy(false) +{ + SetDescription(wstringbuilder() << L"Listening at UDP port " << port); + + m_overlapped.hEvent = m_event.get(); + m_wsaBuf[0].len = static_cast(m_buffer.size()); + m_wsaBuf[0].buf = m_buffer.data(); + + sockaddr_in sa; + sa.sin_family = AF_INET; + sa.sin_addr.s_addr = htonl(INADDR_ANY); + sa.sin_port = htons(u_short(port)); + Win32::bind(m_socket, sa); +} + +HANDLE SocketReader::GetHandle() const +{ + return m_event.get(); +} + +std::string bufferToString(const char* buffer, int size) +{ + size_t length = strnlen(buffer, size); + return std::string(buffer, length); +} + +void SocketReader::Notify() +{ + if (m_busy) + { + int len = CompleteReceive(); + Add(0, GetProcessText(), bufferToString(m_buffer.data(), len)); + } + + for (;;) + { + int len = BeginReceive(); + if (len < 0) + { + return; + } + Add(0, GetProcessText(), bufferToString(m_buffer.data(), len)); + } +} + +int SocketReader::BeginReceive() +{ + assert(!m_busy); + DWORD count; + DWORD flags = 0; + m_fromLen = sizeof(m_from); + if (Win32::WSARecvFrom(m_socket, m_wsaBuf, 1, &count, &flags, m_from, m_fromLen, &m_overlapped, nullptr)) + { + return count; + } + + m_busy = true; + return -1; +} + +int SocketReader::CompleteReceive() +{ + assert(m_busy); + DWORD flags = 0; + auto count = Win32::WSAGetOverlappedResult(m_socket, m_overlapped, false, flags); + m_busy = false; + return count; +} + +std::string SocketReader::GetProcessText() const +{ + return stringbuilder() << "[UDP " << inet_ntoa(m_from.sin_addr) << ":" << ntohs(m_from.sin_port) << "]"; +} + +} // namespace debugviewpp +} // namespace fusion diff --git a/application/DebugViewppTest/CMakeLists.txt b/application/DebugViewppTest/CMakeLists.txt index f9a9e71e..b6cc104c 100644 --- a/application/DebugViewppTest/CMakeLists.txt +++ b/application/DebugViewppTest/CMakeLists.txt @@ -3,14 +3,14 @@ project(DebugviewppTest) add_executable(${PROJECT_NAME} DebugView++Test.cpp) target_link_libraries(${PROJECT_NAME} - PRIVATE - project::definitions - project::compile_features - project::compile_options - nuget::boost_test - dv::library - dv::indexedstorage - CobaltFusion +PRIVATE + project::definitions + project::compile_features + project::compile_options + nuget::boost_test + dv::library + dv::indexedstorage + CobaltFusion ) target_include_directories(${PROJECT_NAME} PUBLIC diff --git a/application/DebugViewppTest/DebugView++Test.cpp b/application/DebugViewppTest/DebugView++Test.cpp index 2b5a6592..9e80f604 100644 --- a/application/DebugViewppTest/DebugView++Test.cpp +++ b/application/DebugViewppTest/DebugView++Test.cpp @@ -1,762 +1,835 @@ -// (C) Copyright Gert-Jan de Vos and Jan Wilmans 2013. -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE_1_0.txt or copy at -// http://www.boost.org/LICENSE_1_0.txt) - -#define BOOST_TEST_MODULE DebugView++ Lib Unit Test - -#include - -#include -#include -#include -#include - -#include "Win32/Utilities.h" -#include "Win32/Win32Lib.h" -#include "CobaltFusion/stringbuilder.h" -#include "CobaltFusion/ExecutorClient.h" -#include "CobaltFusion/Executor.h" -#include "IndexedStorageLib/IndexedStorage.h" -#include "DebugViewppLib/ProcessInfo.h" -#include "DebugViewppLib/DBWinBuffer.h" -#include "DebugViewppLib/LogSources.h" -#include "DebugViewppLib/LogSource.h" -#include "DebugViewppLib/TestSource.h" -#include "DebugViewppLib/VectorLineBuffer.h" -#include "DebugViewppLib/LogFile.h" -#include "DebugViewppLib/FileIO.h" -#include "DebugViewppLib/Conversions.h" -#include "CobaltFusion/scope_guard.h" - -namespace fusion { -namespace debugviewpp { - -BOOST_AUTO_TEST_SUITE(DebugViewPlusPlusLib) - -std::string GetTestString(size_t i) -{ - return stringbuilder() << "BB_TEST_ABCDEFGHI_EE_" << i; -} - -class TestLineBuffer : public LineBuffer -{ -public: - TestLineBuffer(size_t size) : - LineBuffer(size) - { - } - - virtual ~TestLineBuffer() = default; - - virtual void WaitForReaderTimeout() - { - throw std::exception("WaitForReader timeout"); - } -}; - - -class ScopedTimezoneBias -{ -public: - ScopedTimezoneBias(LONG Bias) - { - Win32::SetPrivilege(SE_TIME_ZONE_NAME, true); - GetTimeZoneInformation(&m_tz); - TIME_ZONE_INFORMATION tz = m_tz; - tz.Bias = Bias; - SetTimeZoneInformation(&tz); - } - - ~ScopedTimezoneBias() - { - SetTimeZoneInformation(&m_tz); - Win32::SetPrivilege(SE_TIME_ZONE_NAME, false); - } - -private: - TIME_ZONE_INFORMATION m_tz; -}; - -std::string GetTestFileName() -{ - using namespace std::filesystem; - return absolute(path("SaveLoadLogFile_unique_test_filename")).string(); -} - -std::string SaveLogFile(const LogFile& logfile) -{ - auto filename = GetTestFileName(); - std::ofstream fs; - OpenLogFile(fs, WStr(filename), OpenMode::Truncate); - const int count = logfile.Count(); - for (int i = 0; i < count; ++i) - { - auto msg = logfile[i]; - WriteLogFileMessage(fs, msg.time, msg.systemTime, msg.processId, msg.processName, msg.text); - } - fs.close(); - return filename; -} - -std::string AppendLogFile(const LogFile& logfile) -{ - auto filename = GetTestFileName(); - std::ofstream fs; - OpenLogFile(fs, WStr(filename), OpenMode::Append); - int count = logfile.Count(); - for (int i = 0; i < count; ++i) - { - auto msg = logfile[i]; - WriteLogFileMessage(fs, msg.time, msg.systemTime, msg.processId, msg.processName, msg.text); - } - fs.close(); - return filename; -} - -LogFile LoadLogFile(const std::string& filename) -{ - // load file test - std::ifstream file(filename); - Line line; - line.processName = "process"; - line.systemTime = Win32::GetSystemTimeAsFileTime(); - LogFile result; - ReadLogFileMessage(file, line); // ignore header - while (ReadLogFileMessage(file, line)) - result.Add(Message(line.time, line.systemTime, line.pid, line.processName, line.message)); - return result; -} - -bool AreEqual(const Message& a, const Message& b) -{ - if (std::fabs(a.time - b.time) > 0.000001) - return false; - if (GetTimeText(a.systemTime) != GetTimeText(b.systemTime)) - return false; - if (a.processId != b.processId) - return false; - if (a.processName != b.processName) - return false; - if (a.text != b.text) - return false; - // if (a.color != b.color) return false; // not stored in file - return true; -} - -bool AreEqual(const LogFile& a, const LogFile& b) -{ - if (a.Count() != b.Count()) - return false; - int count = a.Count(); - for (int i = 0; i < count; ++i) - { - if (!AreEqual(a[i], b[i])) - return false; - } - return true; -} - -BOOST_AUTO_TEST_CASE(TimeZone) -{ - Timer timer; - LogFile logFile; - auto t1 = Win32::GetSystemTimeAsFileTime(); - logFile.Add(Message(timer.Get(), t1, 0, "processname", "test message 1")); - auto t2 = Win32::GetSystemTimeAsFileTime(); - logFile.Add(Message(timer.Get(), t2, 0, "processname", "test message 2")); - auto t3 = Win32::GetSystemTimeAsFileTime(); - logFile.Add(Message(timer.Get(), t3, 0, "processname", "test message 3")); - auto t4 = Win32::GetSystemTimeAsFileTime(); - logFile.Add(Message(0, t4, 0, "processname", "message 4 (zero time message)")); - - BOOST_TEST_MESSAGE("UTC"); - { - ScopedTimezoneBias(0); - auto result = LoadLogFile(SaveLogFile(logFile)); - BOOST_TEST(AreEqual(result, logFile)); - } - BOOST_TEST_MESSAGE("(UTC-8) Fremond"); - { - ScopedTimezoneBias(-480); - auto result = LoadLogFile(SaveLogFile(logFile)); - BOOST_TEST(AreEqual(result, logFile)); - } - BOOST_TEST_MESSAGE("(UTC+8:30) Pyongyang Standard Time"); - { - ScopedTimezoneBias(+510); - auto result = LoadLogFile(SaveLogFile(logFile)); - BOOST_TEST(AreEqual(result, logFile)); - } - BOOST_TEST_MESSAGE("test boundry case (UTC-12) Baker- / Howland Island (uninhabited islands belonging to the United States)"); - { - ScopedTimezoneBias(-720); - auto result = LoadLogFile(SaveLogFile(logFile)); - BOOST_TEST(AreEqual(result, logFile)); - } - BOOST_TEST_MESSAGE("test boundry case (UTC+14) Kiritimati (Christmas Island, also uninhabited, part of the Kiribati Line Islands)"); - { - ScopedTimezoneBias(+720); - auto result = LoadLogFile(SaveLogFile(logFile)); - BOOST_TEST(AreEqual(result, logFile)); - } - - // test crossing timezones - BOOST_TEST_MESSAGE("test sending a logfile into the past (UTC -> UTC-8)"); - { - auto filename = SaveLogFile(logFile); - ScopedTimezoneBias(-480); - auto result = LoadLogFile(filename); - BOOST_TEST(AreEqual(result, logFile)); - } - - // test crossing timezones - BOOST_TEST_MESSAGE("test sending a logfile into the future (UTC-8 -> UTC)"); - { - std::string filename; - { - ScopedTimezoneBias(-480); - filename = SaveLogFile(logFile); - } - auto result = LoadLogFile(filename); - BOOST_TEST(AreEqual(result, logFile)); - } -} - -BOOST_AUTO_TEST_CASE(HandleTest) -{ - static const HANDLE nullHandle = nullptr; - - HANDLE rawHandle = ::OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, GetCurrentProcessId()); - Win32::Handle handle(rawHandle); - { - BOOST_TEST(handle.get() == rawHandle); - Win32::Handle handle1(std::move(handle)); - BOOST_TEST(handle.get() == nullHandle); - BOOST_TEST(handle1.get() == rawHandle); - { - Win32::Handle handle2(std::move(handle1)); - handle2.release(); - BOOST_TEST(handle2.get() == nullHandle); - } - BOOST_TEST(handle1.get() == nullHandle); - } - - { - Win32::Handle handle1; - { - HANDLE zero = nullptr; - Win32::Handle zeroHandle(zero); - BOOST_TEST(zeroHandle.get() == nullHandle); - handle1 = std::move(zeroHandle); - BOOST_TEST(zeroHandle.get() == nullHandle); - BOOST_TEST(handle1.get() == nullHandle); - } - // this scope ensures having a Handle leave scope that had its guts ripped out by std::move - // will not cause nullpointers or exeptions - } -} - -BOOST_AUTO_TEST_CASE(LineBufferTest1) -{ - TestLineBuffer buffer(64); - FILETIME ft; - ft.dwLowDateTime = 42; - ft.dwHighDateTime = 43; - buffer.Add(42.0, ft, nullptr, "test", nullptr); - - auto lines = buffer.GetLines(); - auto& line = lines[0]; - BOOST_TEST(line.time == 42.0); - BOOST_TEST(line.systemTime.dwLowDateTime == DWORD(42)); - BOOST_TEST(line.systemTime.dwHighDateTime == DWORD(43)); - BOOST_TEST(buffer.Empty()); -} - -BOOST_AUTO_TEST_CASE(LineBufferTest2) // test overflows boosttestui with log-output locking its GUI up temporarily -{ - TestLineBuffer buffer(600); - Timer timer; - std::cout << "LineBufferTest2 running..." << std::endl; - for (int j = 0; j < 100; ++j) - { - // BOOST_TEST_MESSAGE("j: " << j << "\n"); - - for (int i = 0; i < 17; ++i) - { - // BOOST_TEST_MESSAGE("i: " << i << "\n"); - FILETIME ft; - ft.dwLowDateTime = 43; - ft.dwHighDateTime = 44; - buffer.Add(42.0, ft, nullptr, "test", nullptr); - } - - auto lines = buffer.GetLines(); - BOOST_TEST(lines.size() == 17); - for (auto& line : lines) - { - BOOST_TEST(line.time == 42.0); - BOOST_TEST(line.systemTime.dwLowDateTime == DWORD(43)); - BOOST_TEST(line.systemTime.dwHighDateTime == DWORD(44)); - } - } -} - -BOOST_AUTO_TEST_CASE(IndexedStorageRandomAccess) -{ - using namespace indexedstorage; - - size_t testSize = 10000; - auto testMax = testSize - 1; - - std::mt19937 generator; - std::uniform_int_distribution distribution(0, testMax); - SnappyStorage s; - for (size_t i = 0; i < testSize; ++i) - s.Add(GetTestString(i)); - - // random access can be somewhat slow in debug-mode, this is expected behaviour - bool failed = false; - for (size_t i = 0; i < testSize / 10; ++i) - { - size_t j = distribution(generator); // generates number in the range 0..testMax - if (s[j] != GetTestString(j)) - { - failed = true; - break; - }; - } - BOOST_TEST(!failed); -} - -BOOST_AUTO_TEST_CASE(IndexedStorageCompression) -{ - using namespace indexedstorage; - - // the memory allocator will mess up test results is < 64 kb is allocated - // make sure SnappyStorage allocates at least ~500kb for reproducable results - - // this test is indicative only, on average the SnappyStorage should allocate at most 50% of memory compared to a normal vector. - // since GetTestString returns an overly simpe-to-compress string, it will appear to perform insanely good. - - size_t testSize = 100000; - VectorStorage v; - SnappyStorage s; - - size_t m0 = ProcessInfo::GetPrivateBytes(); - - for (size_t i = 0; i < testSize; ++i) - v.Add(GetTestString(i)); - - size_t m1 = ProcessInfo::GetPrivateBytes(); - size_t usedByVector = m1 - m0; - BOOST_TEST_MESSAGE("VectorStorage requires: " << usedByVector / 1024 << " kB"); - - for (size_t i = 0; i < testSize; ++i) - s.Add(GetTestString(i)); - - size_t m2 = ProcessInfo::GetPrivateBytes(); - size_t usedBySnappy = m2 - m1; - - BOOST_TEST_MESSAGE("SnappyStorage requires: " << usedBySnappy / 1024 << " kB (" << (100 * usedBySnappy) / usedByVector << "%)"); - BOOST_TEST(size_t(0.50 * usedByVector) > usedBySnappy); -} - -// execute as: -// "DebugView++Test.exe" --log_level=test_suite --run_test=*/LogSourcesReceiveMessages -BOOST_AUTO_TEST_CASE(LogSourcesReceiveMessages) -{ - ActiveExecutorClient executor; - LogSources logsources(executor, false); - LogSource* logsource; - executor.Call([&] { logsource = logsources.AddTestSource(); }); - - Timer timer; - logsource->Add(timer.Get(), Win32::GetSystemTimeAsFileTime(), 0, "processname", "message 1"); - logsource->Add(timer.Get(), Win32::GetSystemTimeAsFileTime(), 0, "processname", "message 2"); - logsource->Add(timer.Get(), Win32::GetSystemTimeAsFileTime(), 0, "processname", "message 3"); - BOOST_TEST_MESSAGE("3 lines added."); - - Lines lines; - executor.Call([&] { lines = logsources.GetLines(); }); - BOOST_TEST_MESSAGE("received: " << lines.size() << " lines."); - - BOOST_TEST(lines.size() == 3); - - for (auto& line : lines) - { - BOOST_TEST_MESSAGE("line: " << line.message); - } - - const int testsize = 1000; - BOOST_TEST_MESSAGE("Write " << testsize << " lines..."); - for (int i = 0; i < testsize; ++i) - { - logsource->Add(timer.Get(), Win32::GetSystemTimeAsFileTime(), 0, "processname", "TESTSTRING 1234\n"); - } - - Lines morelines; - executor.Call([&] { morelines = logsources.GetLines(); }); - - BOOST_TEST_MESSAGE("received: " << morelines.size() << " lines."); - BOOST_TEST(morelines.size() == testsize); -} - -BOOST_AUTO_TEST_CASE(LogSourcesCharacterPreservation) -{ - ActiveExecutorClient executor; - LogSources logsources(executor, false); - LogSource* logsource; - executor.Call([&] { logsource = logsources.AddTestSource(); }); - - Timer timer; - logsource->Add(timer.Get(), Win32::GetSystemTimeAsFileTime(), 0, "processname", "TrailingSpace "); - logsource->Add(timer.Get(), Win32::GetSystemTimeAsFileTime(), 0, "processname", "TrailingTab\t"); - - Lines lines; - executor.Call([&] { lines = logsources.GetLines(); }); - BOOST_TEST(lines.size() == 2); - BOOST_TEST(lines[0].message == "TrailingSpace "); // space preserved - BOOST_TEST(lines[1].message == "TrailingTab\t"); // tab preserved -} - -BOOST_AUTO_TEST_CASE(LogSourcesTabHandling) -{ - ActiveExecutorClient executor; - LogSources logsources(executor, false); - LogSource* logsource; - executor.Call([&] { logsource = logsources.AddTestSource(); }); - - Timer timer; - logsource->Add(timer.Get(), Win32::GetSystemTimeAsFileTime(), 0, "processname", "\tTabPrefix"); - logsource->Add(timer.Get(), Win32::GetSystemTimeAsFileTime(), 0, "processname", "\t\tTwoTabsPrefixed"); - - Lines lines; - executor.Call([&] { lines = logsources.GetLines(); }); - BOOST_TEST(lines.size() == 2); - BOOST_TEST(lines[0].message == "\tTabPrefix"); // space preserved - BOOST_TEST(lines[1].message == "\t\tTwoTabsPrefixed"); // space preserved -} - -BOOST_AUTO_TEST_CASE(LogSourcesNewLineHandling) -{ - ActiveExecutorClient executor; - LogSources logsources(executor, false); - LogSource* logsource; - executor.Call([&] { logsource = logsources.AddTestSource(); }); - - Timer timer; - logsource->Add(timer.Get(), Win32::GetSystemTimeAsFileTime(), 0, "processname", "NewLinePostfix\n"); - logsource->Add(timer.Get(), Win32::GetSystemTimeAsFileTime(), 0, "processname", "NewLineCarriageReturnPostfix\n\r"); - logsource->Add(timer.Get(), Win32::GetSystemTimeAsFileTime(), 0, "processname", "CarriageReturnNewLinePostfix\r\n"); - - Lines lines; - executor.Call([&] { lines = logsources.GetLines(); }); - BOOST_TEST(lines.size() == 3); - BOOST_TEST(lines[0].message == "NewLinePostfix"); - BOOST_TEST(lines[1].message == "NewLineCarriageReturnPostfix"); - BOOST_TEST(lines[2].message == "CarriageReturnNewLinePostfix"); -} - -std::wstring GetExecutePath() -{ - auto path = absolute(std::filesystem::path(Win32::GetCommandLineArguments()[0])); - return path.remove_filename().c_str(); -} - -BOOST_AUTO_TEST_CASE(LogSourceLoopback) -{ - using namespace std::chrono_literals; - - Lines lines; - auto executor = std::make_unique(); - { - LogSources logsources(*executor, true); - executor->Call([&] { logsources.AddDBWinReader(false); }); - executor->Call([&] { logsources.AddTestSource(); }); - executor->Call([&] { logsources.SetAutoNewLine(true); }); - - executor->Call([&] { logsources.AddMessage("Test message 1"); }); - executor->Call([&] { logsources.AddMessage("Test message 2"); }); - std::this_thread::sleep_for(200ms); - executor->Call([&] { lines = logsources.GetLines(); }); - - // Abort will remove the source and no messages can be received from remove sources - executor->Call([&] { logsources.Abort(); }); - } - executor.reset(); - - BOOST_TEST(lines.size() == 2); -} - -BOOST_AUTO_TEST_CASE(LogSourceDBWinReader) -{ - using namespace std::chrono_literals; - - std::string dbgMsgSrc = stringbuilder() << GetExecutePath() << "\\DbgMsgSrc.exe"; - BOOST_TEST(FileExists(dbgMsgSrc.c_str())); - std::string cmd = stringbuilder() << dbgMsgSrc << " -n"; - auto executor = std::make_unique(); - Lines lines; - { - LogSources logsources(*executor, true); - executor->Call([&] { logsources.SetAutoNewLine(true); }); - executor->Call([&] { logsources.AddDBWinReader(false); }); - system(cmd.c_str()); - executor->Call([&] { logsources.Abort(); }); - executor->Call([&] { lines = logsources.GetLines(); }); - } - executor.reset(); - - BOOST_TEST(lines.size() == 1); -} - -std::string CreateTestFile() -{ - Timer timer; - LogFile logFile; - logFile.Add(Message(timer.Get(), Win32::GetSystemTimeAsFileTime(), 0, "processname", "test message 1")); - logFile.Add(Message(timer.Get(), Win32::GetSystemTimeAsFileTime(), 0, "processname", "test message 2")); - logFile.Add(Message(timer.Get(), Win32::GetSystemTimeAsFileTime(), 0, "processname", "test message 3")); - logFile.Add(Message(0, Win32::GetSystemTimeAsFileTime(), 0, "processname", "message 4 (zero time message)")); - return SaveLogFile(logFile); -} - -std::string AppendToTestFile() -{ - Timer timer; - LogFile logFile; - logFile.Add(Message(timer.Get(), Win32::GetSystemTimeAsFileTime(), 0, "processname", "test message appended")); - return AppendLogFile(logFile); -} - -std::vector GetNewLineOffsets(const std::string& filename) -{ - std::vector result; - std::ifstream file(filename); - Line line; - while (ReadLogFileMessage(file, line)) - result.push_back(static_cast(file.tellg())); - return result; -} - -void RemoveLinesFromFile(const std::string& filename, int linesToRemove) -{ - auto offsets = GetNewLineOffsets(filename); - auto offset = offsets[offsets.size() - 1 - linesToRemove]; - Win32::HFile(GetTestFileName()).resize(offset); -} - -BOOST_AUTO_TEST_CASE(LogSourceAnyFileReader) -{ - using namespace std::chrono_literals; - auto executor = std::make_unique(); - LogSources logsources(*executor, true); - auto filename = CreateTestFile(); - executor->Call([&] { logsources.SetAutoNewLine(true); }); - executor->Call([&] { logsources.AddAnyFileReader(WStr(filename), true); }); - std::this_thread::sleep_for(200ms); - - Lines lines; - executor->Call([&] { lines = logsources.GetLines(); }); - BOOST_TEST(lines.size() == 5); -} - -BOOST_AUTO_TEST_CASE(LogSourceAnyFileReaderResychronizeShrinkingFile) -{ - using namespace std::chrono_literals; - auto executor = std::make_unique(); - LogSources logsources(*executor, true); - auto filename = CreateTestFile(); - executor->Call([&] { logsources.SetAutoNewLine(true); }); - executor->Call([&] { logsources.AddAnyFileReader(WStr(filename), true); }); - std::this_thread::sleep_for(200ms); - - Lines lines; - executor->Call([&] { lines = logsources.GetLines(); }); - BOOST_TEST(lines.size() == 5); - - RemoveLinesFromFile(GetTestFileName(), 2); - AppendToTestFile(); - AppendToTestFile(); - AppendToTestFile(); - - std::this_thread::sleep_for(200ms); - executor->Call([&] { lines = logsources.GetLines(); }); - BOOST_TEST(lines.size() == 4); -} - -std::string GetTestFileAsString() -{ - std::ifstream file(GetTestFileName()); - std::stringstream ss; - ss << file.rdbuf(); - return ss.str(); -} - -BOOST_AUTO_TEST_CASE(LogSourceAnyFileReaderRewriteByteByByte) -{ - using namespace std::chrono_literals; - auto executor = std::make_unique(); - LogSources logsources(*executor, true); - auto filename = CreateTestFile(); - executor->Call([&] { logsources.SetAutoNewLine(true); }); - executor->Call([&] { logsources.AddAnyFileReader(WStr(filename), true); }); - std::this_thread::sleep_for(200ms); - - { - Lines lines; - executor->Call([&] { lines = logsources.GetLines(); }); - BOOST_TEST(lines.size() == 5); - } - auto content = GetTestFileAsString(); - - { - std::ofstream fs; - fs.open(GetTestFileName(), std::ofstream::trunc); - fs.close(); - } - std::this_thread::sleep_for(100ms); - for (char c : content) - { - std::ofstream fs; - fs.open(GetTestFileName(), std::ofstream::app); - fs << c; - // just flushing the fstream doesn't force a trigger of Find....ChangeNotification, but closing the fstream does the trick - fs.flush(); - fs.close(); - Sleep(20); - } - - std::this_thread::sleep_for(200ms); - Lines lines; - executor->Call([&] { lines = logsources.GetLines(); }); - for (auto& line : lines) - { - BOOST_TEST(line.message.find("xception") == std::string::npos); - // std::cout << line.message << std::endl; - } - BOOST_TEST(lines.size() == 6); - BOOST_TEST(lines.at(0).message.back() == '0'); // line ends in '...offset 0' - BOOST_TEST(lines.at(1).message == "File Identification Header, DebugView++ Format Version 1"); - BOOST_TEST(lines.at(2).message == "test message 1"); - BOOST_TEST(lines.at(3).message == "test message 2"); - BOOST_TEST(lines.at(4).message == "test message 3"); - BOOST_TEST(lines.at(5).message == "message 4 (zero time message)"); -} - -std::string CreateAsciiTestFile() -{ - auto filename = GetTestFileName(); - std::ofstream fs; - fs.open(filename, std::ofstream::trunc); - fs << "This is the first line\n"; - fs << "\n"; // empty line - fs << "This is the last line\n"; - return filename; -} - -BOOST_AUTO_TEST_CASE(LogSourceAnyFileReaderEmptyLines) -{ - using namespace std::chrono_literals; - auto executor = std::make_unique(); - LogSources logsources(*executor, true); - auto filename = CreateAsciiTestFile(); - executor->Call([&] { logsources.SetAutoNewLine(true); }); - executor->Call([&] { logsources.AddAnyFileReader(WStr(filename), true); }); - std::this_thread::sleep_for(200ms); - - { - Lines lines; - executor->Call([&] { lines = logsources.GetLines(); }); - BOOST_TEST(lines.size() == 4); - BOOST_TEST(lines.at(0).message.find("tailing") != std::string::npos); - BOOST_TEST(lines.at(1).message.find("first") != std::string::npos); - BOOST_TEST(lines.at(2).message == "", "The expected newline is gone!"); - BOOST_TEST(lines.at(3).message.find("last") != std::string::npos); - for (auto& line : lines) - { - BOOST_TEST(line.message.find("xception") == std::string::npos); - std::cout << line.message << std::endl; - } - } -} - -BOOST_AUTO_TEST_CASE(LogSourceLoopbackOrdering) -{ - using namespace std::chrono_literals; - auto executor = std::make_unique(); - LogSources logsources(*executor, true); - auto filename = CreateAsciiTestFile(); - executor->Call([&] { logsources.SetAutoNewLine(true); }); - executor->Call([&] { logsources.AddMessage("Loopback message 1"); }); - executor->Call([&] { logsources.AddMessage("Loopback message 2"); }); - executor->Call([&] { logsources.AddMessage("Loopback message 3"); }); - executor->Call([&] { logsources.AddAnyFileReader(WStr(filename), true); }); - std::this_thread::sleep_for(200ms); - Lines lines; - executor->Call([&] { lines = logsources.GetLines(); }); - - BOOST_TEST(lines.at(0).message == "Loopback message 1"); - BOOST_TEST(lines.at(1).message == "Loopback message 2"); - BOOST_TEST(lines.at(2).message == "Loopback message 3"); - BOOST_TEST(lines.at(3).message.find("tailing") != std::string::npos); - BOOST_TEST(lines.at(4).message.find("first") != std::string::npos); - BOOST_TEST(lines.at(5).message == "", "The expected newline is gone!"); - BOOST_TEST(lines.at(6).message.find("last") != std::string::npos); - for (auto& line : lines) - { - std::cout << line.message << std::endl; - } -} - -#pragma warning(disable : 4996) - -#ifndef _HAS_CXX17 - -std::string CreateUTF16LETestFile(int linecount) -{ - auto filename = GetTestFileName(); - std::wfstream fs; - fs.imbue(std::locale(fs.getloc(), new std::codecvt_utf16)); - fs.open(filename, std::ios::binary | std::ios::trunc | std::ios::in | std::ios::out); - for (int i = 0; i < linecount; ++i) - { - fs << "This is line number [" << i + 1 << "]\n"; - } - fs.close(); - return filename; -} - -BOOST_AUTO_TEST_CASE(LoadUTF16LE) -{ - using namespace std::chrono_literals; - auto executor = std::make_unique(); - LogSources logsources(*executor, true); - int lineCount = 65000; - auto filename = CreateUTF16LETestFile(lineCount); - executor->Call([&] { logsources.SetAutoNewLine(true); }); - executor->Call([&] { logsources.AddAnyFileReader(WStr(filename), true); }); - - // assumption: should be done in 4 seconds, even in debug mode - size_t totalLines = 0; - for (int i = 0; i < 20; ++i) - { - std::this_thread::sleep_for(200ms); - Lines lines; - executor->Call([&] { lines = logsources.GetLines(); }); - totalLines += lines.size(); - } - - // (lineCount + 1) because the first line looks like: "Started tailing ....txt identified as '...' - BOOST_TEST(totalLines == (lineCount + 1)); -} - -#endif - -// add test simulating MFC application behaviour (pressing pause/unpause lots of times during significant incomming messages) - -BOOST_AUTO_TEST_SUITE_END() - -} // namespace debugviewpp -} // namespace fusion +// (C) Copyright Gert-Jan de Vos and Jan Wilmans 2013. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#define BOOST_TEST_MODULE DebugView++ Lib Unit Test + +#include + +#include +#include +#include +#include +#include + +#include "Win32/Utilities.h" +#include "Win32/Win32Lib.h" +#include "CobaltFusion/stringbuilder.h" +#include "CobaltFusion/ExecutorClient.h" +#include "CobaltFusion/Executor.h" +#include "IndexedStorageLib/IndexedStorage.h" +#include "DebugViewppLib/ProcessInfo.h" +#include "DebugViewppLib/DBWinBuffer.h" +#include "DebugViewppLib/LogSources.h" +#include "DebugViewppLib/LogSource.h" +#include "DebugViewppLib/TestSource.h" +#include "DebugViewppLib/VectorLineBuffer.h" +#include "DebugViewppLib/LogFile.h" +#include "DebugViewppLib/FileIO.h" +#include "DebugViewppLib/Conversions.h" +#include "CobaltFusion/scope_guard.h" + +namespace fusion { +namespace debugviewpp { + +BOOST_AUTO_TEST_SUITE(DebugViewPlusPlusTest) + +std::string GetTestString(size_t i) +{ + return stringbuilder() << "BB_TEST_ABCDEFGHI_EE_" << i; +} + +class TestLineBuffer : public LineBuffer +{ +public: + TestLineBuffer(size_t size) : + LineBuffer(size) + { + } + + virtual ~TestLineBuffer() = default; + + virtual void WaitForReaderTimeout() + { + throw std::exception("WaitForReader timeout"); + } +}; + + +class ScopedTimezoneBias +{ +public: + ScopedTimezoneBias(LONG Bias) + { + Win32::SetPrivilege(SE_TIME_ZONE_NAME, true); + GetTimeZoneInformation(&m_tz); + TIME_ZONE_INFORMATION tz = m_tz; + tz.Bias = Bias; + SetTimeZoneInformation(&tz); + } + + ~ScopedTimezoneBias() + { + SetTimeZoneInformation(&m_tz); + Win32::SetPrivilege(SE_TIME_ZONE_NAME, false); + } + +private: + TIME_ZONE_INFORMATION m_tz; +}; + +std::string GetTestFileName() +{ + using namespace std::filesystem; + return absolute(path("SaveLoadLogFile_unique_test_filename")).string(); +} + +std::string SaveLogFile(const LogFile& logfile) +{ + auto filename = GetTestFileName(); + std::ofstream fs; + OpenLogFile(fs, WStr(filename), OpenMode::Truncate); + const int count = logfile.Count(); + for (int i = 0; i < count; ++i) + { + auto msg = logfile[i]; + WriteLogFileMessage(fs, msg.time, msg.systemTime, msg.processId, msg.processName, msg.text); + } + fs.close(); + return filename; +} + +std::string AppendLogFile(const LogFile& logfile) +{ + auto filename = GetTestFileName(); + std::ofstream fs; + OpenLogFile(fs, WStr(filename), OpenMode::Append); + int count = logfile.Count(); + for (int i = 0; i < count; ++i) + { + auto msg = logfile[i]; + WriteLogFileMessage(fs, msg.time, msg.systemTime, msg.processId, msg.processName, msg.text); + } + fs.close(); + return filename; +} + +LogFile LoadLogFile(const std::string& filename) +{ + // load file test + std::ifstream file(filename); + Line line; + line.processName = "process"; + line.systemTime = Win32::GetSystemTimeAsFileTime(); + LogFile result; + ReadLogFileMessage(file, line); // ignore header + while (ReadLogFileMessage(file, line)) + result.Add(Message(line.time, line.systemTime, line.pid, line.processName, line.message)); + return result; +} + +bool AreEqual(const Message& a, const Message& b) +{ + if (std::fabs(a.time - b.time) > 0.000001) + return false; + if (GetTimeText(a.systemTime) != GetTimeText(b.systemTime)) + return false; + if (a.processId != b.processId) + return false; + if (a.processName != b.processName) + return false; + if (a.text != b.text) + return false; + // if (a.color != b.color) return false; // not stored in file + return true; +} + +bool AreEqual(const LogFile& a, const LogFile& b) +{ + if (a.Count() != b.Count()) + return false; + int count = a.Count(); + for (int i = 0; i < count; ++i) + { + if (!AreEqual(a[i], b[i])) + return false; + } + return true; +} + +BOOST_AUTO_TEST_CASE(TimeZone) +{ + Timer timer; + LogFile logFile; + auto t1 = Win32::GetSystemTimeAsFileTime(); + logFile.Add(Message(timer.Get(), t1, 0, "processname", "test message 1")); + auto t2 = Win32::GetSystemTimeAsFileTime(); + logFile.Add(Message(timer.Get(), t2, 0, "processname", "test message 2")); + auto t3 = Win32::GetSystemTimeAsFileTime(); + logFile.Add(Message(timer.Get(), t3, 0, "processname", "test message 3")); + auto t4 = Win32::GetSystemTimeAsFileTime(); + logFile.Add(Message(0, t4, 0, "processname", "message 4 (zero time message)")); + + BOOST_TEST_MESSAGE("UTC"); + { + ScopedTimezoneBias(0); + auto result = LoadLogFile(SaveLogFile(logFile)); + BOOST_TEST(AreEqual(result, logFile)); + } + BOOST_TEST_MESSAGE("(UTC-8) Fremond"); + { + ScopedTimezoneBias(-480); + auto result = LoadLogFile(SaveLogFile(logFile)); + BOOST_TEST(AreEqual(result, logFile)); + } + BOOST_TEST_MESSAGE("(UTC+8:30) Pyongyang Standard Time"); + { + ScopedTimezoneBias(+510); + auto result = LoadLogFile(SaveLogFile(logFile)); + BOOST_TEST(AreEqual(result, logFile)); + } + BOOST_TEST_MESSAGE("test boundry case (UTC-12) Baker- / Howland Island (uninhabited islands belonging to the United States)"); + { + ScopedTimezoneBias(-720); + auto result = LoadLogFile(SaveLogFile(logFile)); + BOOST_TEST(AreEqual(result, logFile)); + } + BOOST_TEST_MESSAGE("test boundry case (UTC+14) Kiritimati (Christmas Island, also uninhabited, part of the Kiribati Line Islands)"); + { + ScopedTimezoneBias(+720); + auto result = LoadLogFile(SaveLogFile(logFile)); + BOOST_TEST(AreEqual(result, logFile)); + } + + // test crossing timezones + BOOST_TEST_MESSAGE("test sending a logfile into the past (UTC -> UTC-8)"); + { + auto filename = SaveLogFile(logFile); + ScopedTimezoneBias(-480); + auto result = LoadLogFile(filename); + BOOST_TEST(AreEqual(result, logFile)); + } + + // test crossing timezones + BOOST_TEST_MESSAGE("test sending a logfile into the future (UTC-8 -> UTC)"); + { + std::string filename; + { + ScopedTimezoneBias(-480); + filename = SaveLogFile(logFile); + } + auto result = LoadLogFile(filename); + BOOST_TEST(AreEqual(result, logFile)); + } +} + +BOOST_AUTO_TEST_CASE(HandleTest) +{ + static const HANDLE nullHandle = nullptr; + + HANDLE rawHandle = ::OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, GetCurrentProcessId()); + Win32::Handle handle(rawHandle); + { + BOOST_TEST(handle.get() == rawHandle); + Win32::Handle handle1(std::move(handle)); + BOOST_TEST(handle.get() == nullHandle); + BOOST_TEST(handle1.get() == rawHandle); + { + Win32::Handle handle2(std::move(handle1)); + handle2.release(); + BOOST_TEST(handle2.get() == nullHandle); + } + BOOST_TEST(handle1.get() == nullHandle); + } + + { + Win32::Handle handle1; + { + HANDLE zero = nullptr; + Win32::Handle zeroHandle(zero); + BOOST_TEST(zeroHandle.get() == nullHandle); + handle1 = std::move(zeroHandle); + BOOST_TEST(zeroHandle.get() == nullHandle); + BOOST_TEST(handle1.get() == nullHandle); + } + // this scope ensures having a Handle leave scope that had its guts ripped out by std::move + // will not cause nullpointers or exeptions + } +} + +BOOST_AUTO_TEST_CASE(LineBufferTest1) +{ + TestLineBuffer buffer(64); + FILETIME ft; + ft.dwLowDateTime = 42; + ft.dwHighDateTime = 43; + buffer.Add(42.0, ft, nullptr, "test", nullptr); + + auto lines = buffer.GetLines(); + auto& line = lines[0]; + BOOST_TEST(line.time == 42.0); + BOOST_TEST(line.systemTime.dwLowDateTime == DWORD(42)); + BOOST_TEST(line.systemTime.dwHighDateTime == DWORD(43)); + BOOST_TEST(buffer.Empty()); +} + +BOOST_AUTO_TEST_CASE(LineBufferTest2) // test overflows boosttestui with log-output locking its GUI up temporarily +{ + TestLineBuffer buffer(600); + Timer timer; + std::cout << "LineBufferTest2 running..." << std::endl; + for (int j = 0; j < 100; ++j) + { + // BOOST_TEST_MESSAGE("j: " << j << "\n"); + + for (int i = 0; i < 17; ++i) + { + // BOOST_TEST_MESSAGE("i: " << i << "\n"); + FILETIME ft; + ft.dwLowDateTime = 43; + ft.dwHighDateTime = 44; + buffer.Add(42.0, ft, nullptr, "test", nullptr); + } + + auto lines = buffer.GetLines(); + BOOST_TEST(lines.size() == 17); + for (auto& line : lines) + { + BOOST_TEST(line.time == 42.0); + BOOST_TEST(line.systemTime.dwLowDateTime == DWORD(43)); + BOOST_TEST(line.systemTime.dwHighDateTime == DWORD(44)); + } + } +} + +BOOST_AUTO_TEST_CASE(IndexedStorageRandomAccess) +{ + using namespace indexedstorage; + + size_t testSize = 10000; + auto testMax = testSize - 1; + + std::mt19937 generator; + std::uniform_int_distribution distribution(0, testMax); + SnappyStorage s; + for (size_t i = 0; i < testSize; ++i) + s.Add(GetTestString(i)); + + // random access can be somewhat slow in debug-mode, this is expected behaviour + bool failed = false; + for (size_t i = 0; i < testSize / 10; ++i) + { + size_t j = distribution(generator); // generates number in the range 0..testMax + if (s[j] != GetTestString(j)) + { + failed = true; + break; + }; + } + BOOST_TEST(!failed); +} + +BOOST_AUTO_TEST_CASE(IndexedStorageCompression) +{ + using namespace indexedstorage; + + // the memory allocator will mess up test results is < 64 kb is allocated + // make sure SnappyStorage allocates at least ~500kb for reproducable results + + // this test is indicative only, on average the SnappyStorage should allocate at most 50% of memory compared to a normal vector. + // since GetTestString returns an overly simpe-to-compress string, it will appear to perform insanely good. + + size_t testSize = 100000; + VectorStorage v; + SnappyStorage s; + + size_t m0 = ProcessInfo::GetPrivateBytes(); + + for (size_t i = 0; i < testSize; ++i) + v.Add(GetTestString(i)); + + size_t m1 = ProcessInfo::GetPrivateBytes(); + size_t usedByVector = m1 - m0; + BOOST_TEST_MESSAGE("VectorStorage requires: " << usedByVector / 1024 << " kB"); + + for (size_t i = 0; i < testSize; ++i) + s.Add(GetTestString(i)); + + size_t m2 = ProcessInfo::GetPrivateBytes(); + size_t usedBySnappy = m2 - m1; + + BOOST_TEST_MESSAGE("SnappyStorage requires: " << usedBySnappy / 1024 << " kB (" << (100 * usedBySnappy) / usedByVector << "%)"); + BOOST_TEST(size_t(0.50 * usedByVector) > usedBySnappy); +} + +// execute as: +// "DebugView++Test.exe" --log_level=test_suite --run_test=*/LogSourcesReceiveMessages +BOOST_AUTO_TEST_CASE(LogSourcesReceiveMessages) +{ + ActiveExecutorClient executor; + LogSources logsources(executor, false); + LogSource* logsource; + executor.Call([&] { logsource = logsources.AddTestSource(); }); + + Timer timer; + logsource->Add(timer.Get(), Win32::GetSystemTimeAsFileTime(), 0, "processname", "message 1"); + logsource->Add(timer.Get(), Win32::GetSystemTimeAsFileTime(), 0, "processname", "message 2"); + logsource->Add(timer.Get(), Win32::GetSystemTimeAsFileTime(), 0, "processname", "message 3"); + BOOST_TEST_MESSAGE("3 lines added."); + + Lines lines; + executor.Call([&] { lines = logsources.GetLines(); }); + BOOST_TEST_MESSAGE("received: " << lines.size() << " lines."); + + BOOST_TEST(lines.size() == 3); + + for (auto& line : lines) + { + BOOST_TEST_MESSAGE("line: " << line.message); + } + + const int testsize = 1000; + BOOST_TEST_MESSAGE("Write " << testsize << " lines..."); + for (int i = 0; i < testsize; ++i) + { + logsource->Add(timer.Get(), Win32::GetSystemTimeAsFileTime(), 0, "processname", "TESTSTRING 1234\n"); + } + + Lines morelines; + executor.Call([&] { morelines = logsources.GetLines(); }); + + BOOST_TEST_MESSAGE("received: " << morelines.size() << " lines."); + BOOST_TEST(morelines.size() == testsize); +} + +BOOST_AUTO_TEST_CASE(LogSourcesCharacterPreservation) +{ + ActiveExecutorClient executor; + LogSources logsources(executor, false); + LogSource* logsource; + executor.Call([&] { logsource = logsources.AddTestSource(); }); + + Timer timer; + logsource->Add(timer.Get(), Win32::GetSystemTimeAsFileTime(), 0, "processname", "TrailingSpace "); + logsource->Add(timer.Get(), Win32::GetSystemTimeAsFileTime(), 0, "processname", "TrailingTab\t"); + + Lines lines; + executor.Call([&] { lines = logsources.GetLines(); }); + BOOST_TEST(lines.size() == 2); + BOOST_TEST(lines[0].message == "TrailingSpace "); // space preserved + BOOST_TEST(lines[1].message == "TrailingTab\t"); // tab preserved +} + +BOOST_AUTO_TEST_CASE(LogSourcesTabHandling) +{ + ActiveExecutorClient executor; + LogSources logsources(executor, false); + LogSource* logsource; + executor.Call([&] { logsource = logsources.AddTestSource(); }); + + Timer timer; + logsource->Add(timer.Get(), Win32::GetSystemTimeAsFileTime(), 0, "processname", "\tTabPrefix"); + logsource->Add(timer.Get(), Win32::GetSystemTimeAsFileTime(), 0, "processname", "\t\tTwoTabsPrefixed"); + + Lines lines; + executor.Call([&] { lines = logsources.GetLines(); }); + BOOST_TEST(lines.size() == 2); + BOOST_TEST(lines[0].message == "\tTabPrefix"); // space preserved + BOOST_TEST(lines[1].message == "\t\tTwoTabsPrefixed"); // space preserved +} + +BOOST_AUTO_TEST_CASE(LogSourcesNewLineHandling) +{ + ActiveExecutorClient executor; + LogSources logsources(executor, false); + LogSource* logsource; + executor.Call([&] { logsource = logsources.AddTestSource(); }); + + Timer timer; + logsource->Add(timer.Get(), Win32::GetSystemTimeAsFileTime(), 0, "processname", "NewLinePostfix\n"); + logsource->Add(timer.Get(), Win32::GetSystemTimeAsFileTime(), 0, "processname", "NewLineCarriageReturnPostfix\n\r"); + logsource->Add(timer.Get(), Win32::GetSystemTimeAsFileTime(), 0, "processname", "CarriageReturnNewLinePostfix\r\n"); + + Lines lines; + executor.Call([&] { lines = logsources.GetLines(); }); + BOOST_TEST(lines.size() == 3); + BOOST_TEST(lines[0].message == "NewLinePostfix"); + BOOST_TEST(lines[1].message == "NewLineCarriageReturnPostfix"); + BOOST_TEST(lines[2].message == "CarriageReturnNewLinePostfix"); +} + +std::wstring GetExecutePath() +{ + auto path = absolute(std::filesystem::path(Win32::GetCommandLineArguments()[0])); + return path.remove_filename().c_str(); +} + +BOOST_AUTO_TEST_CASE(LogSourceLoopback) +{ + using namespace std::chrono_literals; + + Lines lines; + auto executor = std::make_unique(); + { + LogSources logsources(*executor, true); + executor->Call([&] { logsources.AddDBWinReader(false); }); + executor->Call([&] { logsources.AddTestSource(); }); + executor->Call([&] { logsources.SetAutoNewLine(true); }); + + executor->Call([&] { logsources.AddMessage("Test message 1"); }); + executor->Call([&] { logsources.AddMessage("Test message 2"); }); + std::this_thread::sleep_for(200ms); + executor->Call([&] { lines = logsources.GetLines(); }); + + // Abort will remove the source and no messages can be received from remove sources + executor->Call([&] { logsources.Abort(); }); + } + executor.reset(); + + BOOST_TEST(lines.size() == 2); +} + +BOOST_AUTO_TEST_CASE(LogSourceDBWinReader) +{ + using namespace std::chrono_literals; + + std::string dbgMsgSrc = stringbuilder() << GetExecutePath() << "\\DbgMsgSrc.exe"; + BOOST_TEST(FileExists(dbgMsgSrc.c_str())); + std::string cmd = stringbuilder() << dbgMsgSrc << " -n"; + auto executor = std::make_unique(); + Lines lines; + { + LogSources logsources(*executor, true); + executor->Call([&] { logsources.SetAutoNewLine(true); }); + executor->Call([&] { logsources.AddDBWinReader(false); }); + system(cmd.c_str()); + executor->Call([&] { logsources.Abort(); }); + executor->Call([&] { lines = logsources.GetLines(); }); + } + executor.reset(); + + BOOST_TEST(lines.size() == 1); +} + +BOOST_AUTO_TEST_CASE(LogSourceDBWinReader150KLines) +{ + using namespace std::chrono_literals; + + constexpr int amountOfTestLines = 150000; + auto executor = std::make_unique(); + Lines lines; + { + LogSources logsources(*executor, true); + executor->Call([&] { logsources.SetAutoNewLine(true); }); + executor->Call([&] { logsources.AddDBWinReader(false); }); + + for (int i = 0; i < amountOfTestLines; ++i) + { + std::string message = stringbuilder() << i << '\n'; + OutputDebugStringA(message.c_str()); + } + executor->Call([&] { logsources.Abort(); }); + executor->Call([&] { lines = logsources.GetLines(); }); + } + executor.reset(); + + + for (int i = 0; i < amountOfTestLines; ++i) + { + std::string message = stringbuilder() << i; + if (lines[i].message != message) + { + std::print("'{}' is not '{}'\n", lines[i].message, message); + } + BOOST_TEST(lines[i].message == message); + } + BOOST_TEST(lines.size() == amountOfTestLines); +} + +BOOST_AUTO_TEST_CASE(LogSourceDBWinReader150KLongerLines) +{ + using namespace std::chrono_literals; + + std::string test_string = "ThisIsALongerTestMessageThisIsALongerTestMessageThisIsALongerTestMessageThisIsALongerTestMessage"; + constexpr int amountOfTestLines = 150000; + auto executor = std::make_unique(); + Lines lines; + { + LogSources logsources(*executor, true); + executor->Call([&] { logsources.SetAutoNewLine(true); }); + executor->Call([&] { logsources.AddDBWinReader(false); }); + + for (int i = 0; i < amountOfTestLines; ++i) + { + std::string message = stringbuilder() << i << test_string << test_string << '\n'; + OutputDebugStringA(message.c_str()); + } + executor->Call([&] { logsources.Abort(); }); + executor->Call([&] { lines = logsources.GetLines(); }); + } + executor.reset(); + + + for (int i = 0; i < amountOfTestLines; ++i) + { + std::string message = stringbuilder() << i << test_string << test_string; + if (lines[i].message != message) + { + std::print("'{}' is not '{}'\n", lines[i].message, message); + } + BOOST_TEST(lines[i].message == message); + } + BOOST_TEST(lines.size() == amountOfTestLines); +} + + +std::string CreateTestFile() +{ + Timer timer; + LogFile logFile; + logFile.Add(Message(timer.Get(), Win32::GetSystemTimeAsFileTime(), 0, "processname", "test message 1")); + logFile.Add(Message(timer.Get(), Win32::GetSystemTimeAsFileTime(), 0, "processname", "test message 2")); + logFile.Add(Message(timer.Get(), Win32::GetSystemTimeAsFileTime(), 0, "processname", "test message 3")); + logFile.Add(Message(0, Win32::GetSystemTimeAsFileTime(), 0, "processname", "message 4 (zero time message)")); + return SaveLogFile(logFile); +} + +std::string AppendToTestFile() +{ + Timer timer; + LogFile logFile; + logFile.Add(Message(timer.Get(), Win32::GetSystemTimeAsFileTime(), 0, "processname", "test message appended")); + return AppendLogFile(logFile); +} + +std::vector GetNewLineOffsets(const std::string& filename) +{ + std::vector result; + std::ifstream file(filename); + Line line; + while (ReadLogFileMessage(file, line)) + result.push_back(static_cast(file.tellg())); + return result; +} + +void RemoveLinesFromFile(const std::string& filename, int linesToRemove) +{ + auto offsets = GetNewLineOffsets(filename); + auto offset = offsets[offsets.size() - 1 - linesToRemove]; + Win32::HFile(GetTestFileName()).resize(offset); +} + +BOOST_AUTO_TEST_CASE(LogSourceAnyFileReader) +{ + using namespace std::chrono_literals; + auto executor = std::make_unique(); + LogSources logsources(*executor, true); + auto filename = CreateTestFile(); + executor->Call([&] { logsources.SetAutoNewLine(true); }); + executor->Call([&] { logsources.AddAnyFileReader(WStr(filename), true); }); + std::this_thread::sleep_for(200ms); + + Lines lines; + executor->Call([&] { lines = logsources.GetLines(); }); + BOOST_TEST(lines.size() == 5); +} + +BOOST_AUTO_TEST_CASE(LogSourceAnyFileReaderResychronizeShrinkingFile) +{ + using namespace std::chrono_literals; + auto executor = std::make_unique(); + LogSources logsources(*executor, true); + auto filename = CreateTestFile(); + executor->Call([&] { logsources.SetAutoNewLine(true); }); + executor->Call([&] { logsources.AddAnyFileReader(WStr(filename), true); }); + std::this_thread::sleep_for(200ms); + + Lines lines; + executor->Call([&] { lines = logsources.GetLines(); }); + BOOST_TEST(lines.size() == 5); + + RemoveLinesFromFile(GetTestFileName(), 2); + AppendToTestFile(); + AppendToTestFile(); + AppendToTestFile(); + + std::this_thread::sleep_for(200ms); + executor->Call([&] { lines = logsources.GetLines(); }); + BOOST_TEST(lines.size() == 4); +} + +std::string GetTestFileAsString() +{ + std::ifstream file(GetTestFileName()); + std::stringstream ss; + ss << file.rdbuf(); + return ss.str(); +} + +BOOST_AUTO_TEST_CASE(LogSourceAnyFileReaderRewriteByteByByte, *boost::unit_test::disabled()) +{ + using namespace std::chrono_literals; + auto executor = std::make_unique(); + LogSources logsources(*executor, true); + auto filename = CreateTestFile(); + executor->Call([&] { logsources.SetAutoNewLine(true); }); + executor->Call([&] { logsources.AddAnyFileReader(WStr(filename), true); }); + std::this_thread::sleep_for(200ms); + + { + Lines lines; + executor->Call([&] { lines = logsources.GetLines(); }); + BOOST_TEST(lines.size() == 5); + } + auto content = GetTestFileAsString(); + + { + std::ofstream fs; + fs.open(GetTestFileName(), std::ofstream::trunc); + fs.close(); + } + std::this_thread::sleep_for(100ms); + for (char c : content) + { + std::ofstream fs; + fs.open(GetTestFileName(), std::ofstream::app); + fs << c; + // just flushing the fstream doesn't force a trigger of Find....ChangeNotification, but closing the fstream does the trick + fs.flush(); + fs.close(); + Sleep(20); + } + + std::this_thread::sleep_for(200ms); + Lines lines; + executor->Call([&] { lines = logsources.GetLines(); }); + for (auto& line : lines) + { + BOOST_TEST(line.message.find("xception") == std::string::npos); + // std::cout << line.message << std::endl; + } + BOOST_TEST(lines.size() == 6); + BOOST_TEST(lines.at(0).message.back() == '0'); // line ends in '...offset 0' + BOOST_TEST(lines.at(1).message == "File Identification Header, DebugView++ Format Version 1"); + BOOST_TEST(lines.at(2).message == "test message 1"); + BOOST_TEST(lines.at(3).message == "test message 2"); + BOOST_TEST(lines.at(4).message == "test message 3"); + BOOST_TEST(lines.at(5).message == "message 4 (zero time message)"); +} + +std::string CreateAsciiTestFile() +{ + auto filename = GetTestFileName(); + std::ofstream fs; + fs.open(filename, std::ofstream::trunc); + fs << "This is the first line\n"; + fs << "\n"; // empty line + fs << "This is the last line\n"; + return filename; +} + +BOOST_AUTO_TEST_CASE(LogSourceAnyFileReaderEmptyLines) +{ + using namespace std::chrono_literals; + auto executor = std::make_unique(); + LogSources logsources(*executor, true); + auto filename = CreateAsciiTestFile(); + executor->Call([&] { logsources.SetAutoNewLine(true); }); + executor->Call([&] { logsources.AddAnyFileReader(WStr(filename), true); }); + std::this_thread::sleep_for(200ms); + + { + Lines lines; + executor->Call([&] { lines = logsources.GetLines(); }); + BOOST_TEST(lines.size() == 4); + BOOST_TEST(lines.at(0).message.find("tailing") != std::string::npos); + BOOST_TEST(lines.at(1).message.find("first") != std::string::npos); + BOOST_TEST(lines.at(2).message == "", "The expected newline is gone!"); + BOOST_TEST(lines.at(3).message.find("last") != std::string::npos); + for (auto& line : lines) + { + BOOST_TEST(line.message.find("xception") == std::string::npos); + std::cout << line.message << std::endl; + } + } +} + +BOOST_AUTO_TEST_CASE(LogSourceLoopbackOrdering) +{ + using namespace std::chrono_literals; + auto executor = std::make_unique(); + LogSources logsources(*executor, true); + auto filename = CreateAsciiTestFile(); + executor->Call([&] { logsources.SetAutoNewLine(true); }); + executor->Call([&] { logsources.AddMessage("Loopback message 1"); }); + executor->Call([&] { logsources.AddMessage("Loopback message 2"); }); + executor->Call([&] { logsources.AddMessage("Loopback message 3"); }); + executor->Call([&] { logsources.AddAnyFileReader(WStr(filename), true); }); + std::this_thread::sleep_for(200ms); + Lines lines; + executor->Call([&] { lines = logsources.GetLines(); }); + + BOOST_TEST(lines.at(0).message == "Loopback message 1"); + BOOST_TEST(lines.at(1).message == "Loopback message 2"); + BOOST_TEST(lines.at(2).message == "Loopback message 3"); + BOOST_TEST(lines.at(3).message.find("tailing") != std::string::npos); + BOOST_TEST(lines.at(4).message.find("first") != std::string::npos); + BOOST_TEST(lines.at(5).message == "", "The expected newline is gone!"); + BOOST_TEST(lines.at(6).message.find("last") != std::string::npos); + for (auto& line : lines) + { + std::cout << line.message << std::endl; + } +} + +#pragma warning(disable : 4996) + +#ifndef _HAS_CXX17 + +std::string CreateUTF16LETestFile(int linecount) +{ + auto filename = GetTestFileName(); + std::wfstream fs; + fs.imbue(std::locale(fs.getloc(), new std::codecvt_utf16)); + fs.open(filename, std::ios::binary | std::ios::trunc | std::ios::in | std::ios::out); + for (int i = 0; i < linecount; ++i) + { + fs << "This is line number [" << i + 1 << "]\n"; + } + fs.close(); + return filename; +} + +BOOST_AUTO_TEST_CASE(LoadUTF16LE) +{ + using namespace std::chrono_literals; + auto executor = std::make_unique(); + LogSources logsources(*executor, true); + int lineCount = 65000; + auto filename = CreateUTF16LETestFile(lineCount); + executor->Call([&] { logsources.SetAutoNewLine(true); }); + executor->Call([&] { logsources.AddAnyFileReader(WStr(filename), true); }); + + // assumption: should be done in 4 seconds, even in debug mode + size_t totalLines = 0; + for (int i = 0; i < 20; ++i) + { + std::this_thread::sleep_for(200ms); + Lines lines; + executor->Call([&] { lines = logsources.GetLines(); }); + totalLines += lines.size(); + } + + // (lineCount + 1) because the first line looks like: "Started tailing ....txt identified as '...' + BOOST_TEST(totalLines == (lineCount + 1)); +} + +#endif + +// add test simulating MFC application behaviour (pressing pause/unpause lots of times during significant incomming messages) + +BOOST_AUTO_TEST_SUITE_END() + +} // namespace debugviewpp +} // namespace fusion diff --git a/application/GDIGraphicsPOC/CMakeLists.txt b/application/GDIGraphicsPOC/CMakeLists.txt index 4b11482c..d9307542 100644 --- a/application/GDIGraphicsPOC/CMakeLists.txt +++ b/application/GDIGraphicsPOC/CMakeLists.txt @@ -8,18 +8,18 @@ add_executable(${PROJECT_NAME} ) target_link_libraries(${PROJECT_NAME} - PRIVATE - project::definitions - project::compile_features - project::compile_options - project::gui_link_options - nuget::boost - nuget::wtl - dv::cobaltfusion - dv::tabbingframework - dv::propertygrid - dv::library - dv::win32 +PRIVATE + project::definitions + project::compile_features + project::compile_options + project::gui_link_options + nuget::boost + nuget::wtl + dv::cobaltfusion + dv::tabbingframework + dv::propertygrid + dv::library + dv::win32 ) target_include_directories(${PROJECT_NAME} PUBLIC diff --git a/application/IndexedStorageLib/CMakeLists.txt b/application/IndexedStorageLib/CMakeLists.txt index 82dc2da7..11c0aec4 100644 --- a/application/IndexedStorageLib/CMakeLists.txt +++ b/application/IndexedStorageLib/CMakeLists.txt @@ -4,12 +4,12 @@ add_library(${PROJECT_NAME} IndexedStorage.cpp) add_library(dv::indexedstorage ALIAS ${PROJECT_NAME}) target_link_libraries(${PROJECT_NAME} - PRIVATE - project::definitions - project::compile_features - project::compile_options - nuget::snappy - dv::win32 +PRIVATE + project::definitions + project::compile_features + project::compile_options + nuget::snappy + dv::win32 ) target_include_directories(${PROJECT_NAME} PUBLIC diff --git a/application/Libraries/docopt.cpp/CMakeLists.txt b/application/Libraries/docopt.cpp/CMakeLists.txt index 28da6140..4b31e014 100644 --- a/application/Libraries/docopt.cpp/CMakeLists.txt +++ b/application/Libraries/docopt.cpp/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.1) +cmake_minimum_required(VERSION 3.16) project(docopt.cpp VERSION 0.6.2) include(GNUInstallDirs) @@ -17,7 +17,7 @@ option(USE_BOOST_REGEX "Replace std::regex with Boost.Regex" OFF) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) if(NOT CMAKE_CXX_STANDARD OR CMAKE_CXX_STANDARD LESS 11) - set(CMAKE_CXX_STANDARD 11) + set(CMAKE_CXX_STANDARD 11) endif() #============================================================================ @@ -25,11 +25,11 @@ endif() #============================================================================ set(docopt_SOURCES docopt.cpp) set(docopt_HEADERS - docopt.h - docopt_private.h - docopt_util.h - docopt_value.h - ) + docopt.h + docopt_private.h + docopt_util.h + docopt_value.h + ) #============================================================================ # Compile targets @@ -52,10 +52,10 @@ if(MSVC AND BUILD_SHARED_LIBS) endif() if(USE_BOOST_REGEX) - add_definitions("-DDOCTOPT_USE_BOOST_REGEX") - # This is needed on Linux, where linking a static library into docopt.so - # fails because boost static libs are not compiled with -fPIC - set(Boost_USE_STATIC_LIBS OFF) + add_definitions("-DDOCTOPT_USE_BOOST_REGEX") + # This is needed on Linux, where linking a static library into docopt.so + # fails because boost static libs are not compiled with -fPIC + set(Boost_USE_STATIC_LIBS OFF) find_package(Boost 1.53 REQUIRED COMPONENTS regex) include_directories(${Boost_INCLUDE_DIRS}) target_link_libraries(docopt ${Boost_LIBRARIES}) @@ -65,24 +65,24 @@ endif() # Examples #============================================================================ if(WITH_EXAMPLE) - add_executable(docopt_example examples/naval_fate.cpp) - target_link_libraries(docopt_example docopt) + add_executable(docopt_example examples/naval_fate.cpp) + target_link_libraries(docopt_example docopt) endif() #============================================================================ # Tests #============================================================================ if(WITH_TESTS) - set(TESTPROG "${CMAKE_CURRENT_BINARY_DIR}/run_testcase") - set(TESTCASES "${PROJECT_SOURCE_DIR}/testcases.docopt") - add_executable(run_testcase run_testcase.cpp) - target_link_libraries(run_testcase docopt) - configure_file( - "${PROJECT_SOURCE_DIR}/run_tests.py" - "${CMAKE_CURRENT_BINARY_DIR}/run_tests" - ESCAPE_QUOTES - ) - add_test("Testcases docopt" ${TESTPROG}) + set(TESTPROG "${CMAKE_CURRENT_BINARY_DIR}/run_testcase") + set(TESTCASES "${PROJECT_SOURCE_DIR}/testcases.docopt") + add_executable(run_testcase run_testcase.cpp) + target_link_libraries(run_testcase docopt) + configure_file( + "${PROJECT_SOURCE_DIR}/run_tests.py" + "${CMAKE_CURRENT_BINARY_DIR}/run_tests" + ESCAPE_QUOTES + ) + add_test("Testcases docopt" ${TESTPROG}) endif() #============================================================================ @@ -92,9 +92,9 @@ set(export_name "docopt-targets") # Runtime package install(TARGETS docopt EXPORT ${export_name} - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}) + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}) # Development package install(FILES ${docopt_HEADERS} DESTINATION include/docopt) diff --git a/application/Libraries/docopt.cpp/README.rst b/application/Libraries/docopt.cpp/README.rst index 5fe8b4f6..a598bd58 100644 --- a/application/Libraries/docopt.cpp/README.rst +++ b/application/Libraries/docopt.cpp/README.rst @@ -87,7 +87,7 @@ Alternatively manual installation is done using (unix):: To link *docopt.cpp*, the simplest is to use CMake. The general structure of your ``CMakeLists.txt`` would be as follows:: - cmake_minimum_required(VERSION 3.1) + cmake_minimum_required(VERSION 3.16) project(example) diff --git a/application/Win32Lib/CMakeLists.txt b/application/Win32Lib/CMakeLists.txt index 28b58271..249a6b3b 100644 --- a/application/Win32Lib/CMakeLists.txt +++ b/application/Win32Lib/CMakeLists.txt @@ -4,10 +4,10 @@ add_library(${PROJECT_NAME} Com.cpp Process.cpp Registry.cpp Socket.cpp Utilitie add_library(dv::win32 ALIAS ${PROJECT_NAME}) target_link_libraries(${PROJECT_NAME} - PRIVATE - project::definitions - project::compile_features - project::compile_options +PRIVATE + project::definitions + project::compile_features + project::compile_options ) target_include_directories(win32lib PUBLIC diff --git a/application/Win32Lib/Win32Lib.cpp b/application/Win32Lib/Win32Lib.cpp index d55e19e8..87b83c75 100644 --- a/application/Win32Lib/Win32Lib.cpp +++ b/application/Win32Lib/Win32Lib.cpp @@ -300,13 +300,14 @@ void SetSecurityInfo(HANDLE hObject, SE_OBJECT_TYPE ObjectType, SECURITY_INFORMA #ifdef DACL_MODIFY -//delete DACL at all, so permit Full Access for Everyone +// delete DACL at all, so permit Full Access for Everyone void DeleteObjectDACL(HANDLE hObject) { Win32::SetSecurityInfo(hObject, SE_KERNEL_OBJECT, DACL_SECURITY_INFORMATION, nullptr, nullptr, nullptr, nullptr); } -//add necessary permissions for "Authenticated Users" group (all non-anonymous users) +// add necessary permissions for "Authenticated Users" group (all non-anonymous users) +// and for "All Application Packages", "All Restricted Application Packages" for UWP support void AdjustObjectDACL(HANDLE hObject) { ACL* pOldDACL; @@ -317,20 +318,41 @@ void AdjustObjectDACL(HANDLE hObject) SID_IDENTIFIER_AUTHORITY authNt = SECURITY_NT_AUTHORITY; AllocateAndInitializeSid(&authNt, 1, SECURITY_AUTHENTICATED_USER_RID, 0, 0, 0, 0, 0, 0, 0, &pSid); - EXPLICIT_ACCESS ea = {}; - ea.grfAccessMode = GRANT_ACCESS; - ea.grfAccessPermissions = GENERIC_READ | GENERIC_WRITE | GENERIC_EXECUTE; - ea.grfInheritance = NO_INHERITANCE; - ea.Trustee.TrusteeType = TRUSTEE_IS_GROUP; - ea.Trustee.TrusteeForm = TRUSTEE_IS_SID; - ea.Trustee.ptstrName = static_cast(pSid); + PSID pAnyPackageSid = nullptr; + SID_IDENTIFIER_AUTHORITY SIDAuthAppPackage = SECURITY_APP_PACKAGE_AUTHORITY; + AllocateAndInitializeSid(&SIDAuthAppPackage, SECURITY_BUILTIN_APP_PACKAGE_RID_COUNT, + SECURITY_APP_PACKAGE_BASE_RID, SECURITY_BUILTIN_PACKAGE_ANY_PACKAGE, + 0, 0, 0, 0, 0, 0, + &pAnyPackageSid); + + PSID pAnyRestrictedPackageSid = nullptr; + AllocateAndInitializeSid(&SIDAuthAppPackage, SECURITY_BUILTIN_APP_PACKAGE_RID_COUNT, + SECURITY_APP_PACKAGE_BASE_RID, SECURITY_BUILTIN_PACKAGE_ANY_RESTRICTED_PACKAGE, + 0, 0, 0, 0, 0, 0, + &pAnyRestrictedPackageSid); + + EXPLICIT_ACCESS ea[3] = {}; + for (auto& item : ea) + { + item.grfAccessMode = GRANT_ACCESS; + item.grfAccessPermissions = GENERIC_READ | GENERIC_WRITE | GENERIC_EXECUTE; + item.grfInheritance = NO_INHERITANCE; + item.Trustee.TrusteeType = TRUSTEE_IS_GROUP; + item.Trustee.TrusteeForm = TRUSTEE_IS_SID; + } + + ea[0].Trustee.ptstrName = static_cast(pSid); + ea[1].Trustee.ptstrName = static_cast(pAnyPackageSid); + ea[2].Trustee.ptstrName = static_cast(pAnyRestrictedPackageSid); ACL* pNewDACL = nullptr; - SetEntriesInAcl(1, &ea, pOldDACL, &pNewDACL); + SetEntriesInAcl(std::size(ea), ea, pOldDACL, &pNewDACL); Win32::SetSecurityInfo(hObject, SE_KERNEL_OBJECT, DACL_SECURITY_INFORMATION, nullptr, nullptr, pNewDACL, nullptr); FreeSid(pSid); + FreeSid(pAnyPackageSid); + FreeSid(pAnyRestrictedPackageSid); LocalFree(pNewDACL); LocalFree(pSD); } @@ -389,7 +411,7 @@ WaitResult WaitForMultipleObjects(const HANDLE* begin, const HANDLE* end, bool w { ThrowLastError("WaitForMultipleObjects"); } - if (rc >= WAIT_OBJECT_0 && rc < WAIT_OBJECT_0 + count) + if (rc < WAIT_OBJECT_0 + count) { return WaitResult(true, rc - WAIT_OBJECT_0); } @@ -652,13 +674,13 @@ std::wstring GetSEHcodeDescription(DWORD code) case STATUS_NO_MEMORY: return L"EXCEPTION_NO_MEMORY"; case STATUS_ORDINAL_NOT_FOUND : return L"EXCEPTION_ORDINAL_NOT_FOUND"; case STATUS_ENTRYPOINT_NOT_FOUND: return L"EXCEPTION_ENTRYPOINT_NOT_FOUND"; - case STATUS_CONTROL_C_EXIT: return L"EXCEPTION_CONTROL_C_EXIT"; + case STATUS_CONTROL_C_EXIT: return L"EXCEPTION_CONTROL_C_EXIT"; case STATUS_DLL_INIT_FAILED: return L"DllMain returned false"; case STATUS_FLOAT_MULTIPLE_FAULTS: return L"EXCEPTION_FLOAT_MULTIPLE_FAULTS"; case STATUS_FLOAT_MULTIPLE_TRAPS: return L"EXCEPTION_FLOAT_MULTIPLE_TRAPS"; case STATUS_REG_NAT_CONSUMPTION: return L"EXCEPTION_REG_NAT_CONSUMPTION"; case STATUS_STACK_BUFFER_OVERRUN: return L"EXCEPTION_STACK_BUFFER_OVERRUN"; - case STATUS_INVALID_CRUNTIME_PARAMETER: return L"EXCEPTION_INVALID_CRUNTIME_PARAMETER"; + case STATUS_INVALID_CRUNTIME_PARAMETER: return L"EXCEPTION_INVALID_CRUNTIME_PARAMETER"; case STATUS_ASSERTION_FAILURE: return L"EXCEPTION_ASSERTION_FAILURE"; case DBG_EXCEPTION_HANDLED: return L"DBG_EXCEPTION_HANDLED"; case DBG_CONTINUE: return L"DBG_CONTINUE"; diff --git a/application/include/CobaltFusion/CircularBuffer.h b/application/include/CobaltFusion/CircularBuffer.h index 02525ecd..d917a384 100644 --- a/application/include/CobaltFusion/CircularBuffer.h +++ b/application/include/CobaltFusion/CircularBuffer.h @@ -9,8 +9,6 @@ #include #include "noncopyable.h" -#pragma comment(lib, "CobaltFusion.lib") - namespace fusion { class CircularBuffer : fusion::noncopyable diff --git a/application/include/CobaltFusion/JobDispatcher.h b/application/include/CobaltFusion/JobDispatcher.h index a22a39b8..b2f02298 100644 --- a/application/include/CobaltFusion/JobDispatcher.h +++ b/application/include/CobaltFusion/JobDispatcher.h @@ -1,6 +1,6 @@ // (C) Copyright Gert-Jan de Vos and Jan Wilmans 2013. // Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE_1_0.txt or copy at +// (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #pragma once @@ -13,19 +13,17 @@ #include #include -#pragma comment(lib, "CobaltFusion.lib") - namespace fusion { // Two use cases are supported by JobDispatcher -// +// // 1) BackgroundDispatcher // any Queue'd jobs by the caller will then be executed on a background thread until stop is called. // // 2) OnDemandDispatcher // call ExecuteQueuedJobs() to have the queued jobs executed on the caller thread -// - +// + typedef std::function Job; class JobDispatcher @@ -34,7 +32,7 @@ class JobDispatcher JobDispatcher(); virtual ~JobDispatcher(); - // guarentees and all jobs queued before Flush() have been executed + // guarentees and all jobs queued before Flush() have been executed virtual void Flush(); // returns immediately after queueing diff --git a/application/include/CobaltFusion/Timer.h b/application/include/CobaltFusion/Timer.h index 649d5abc..96276c9b 100644 --- a/application/include/CobaltFusion/Timer.h +++ b/application/include/CobaltFusion/Timer.h @@ -10,6 +10,8 @@ namespace fusion { +long long GetTicks(); + class Timer { public: @@ -17,10 +19,9 @@ class Timer void Reset(); double Get(); + double GetTimeSinceOrigin(long long); private: - static long long GetTicks(); - double m_timerUnit; bool m_init; long long m_offset; diff --git a/application/include/CobaltFusion/stringbuilder.h b/application/include/CobaltFusion/stringbuilder.h index 5ea58dfe..68144f70 100644 --- a/application/include/CobaltFusion/stringbuilder.h +++ b/application/include/CobaltFusion/stringbuilder.h @@ -54,9 +54,9 @@ class wstringbuilder wstringbuilder& operator<<(const std::string& str) { - m_ss << WStr(str); + m_ss << WStr(str).str(); return *this; - } + } operator std::wstring() const { diff --git a/application/include/DebugViewppLib/Debugview_kernel_client.h b/application/include/DebugViewppLib/Debugview_kernel_client.h new file mode 100644 index 00000000..586405d1 --- /dev/null +++ b/application/include/DebugViewppLib/Debugview_kernel_client.h @@ -0,0 +1,75 @@ +// (C) Copyright Gert-Jan de Vos and Jan Wilmans 2013. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// +// this is the dbgv.sys interface +// + +#pragma once + +#include +#include +#include +#include + +#include +#include + +#define FILE_DEVICE_DBGV 0x8305 + +// clang-format off +#define DBGV_CAPTURE_KERNEL CTL_CODE(FILE_DEVICE_DBGV, DWORD(0), METHOD_BUFFERED, FILE_ANY_ACCESS) //0x00 //enable capture kernel +#define DBGV_UNCAPTURE_KERNEL CTL_CODE(FILE_DEVICE_DBGV, DWORD(1), METHOD_BUFFERED, FILE_ANY_ACCESS) //0x04 // +#define DBGV_CLEAR_DISPLAY CTL_CODE(FILE_DEVICE_DBGV, DWORD(2), METHOD_BUFFERED, FILE_ANY_ACCESS) //0x08 //clear display +#define DBGV_READ_LOG CTL_CODE(FILE_DEVICE_DBGV, DWORD(3), METHOD_NEITHER, FILE_ANY_ACCESS) //0x0f //read kernel log +#define DBGV_SET_PASSTHROUGH CTL_CODE(FILE_DEVICE_DBGV, DWORD(4), METHOD_BUFFERED, FILE_ANY_ACCESS) //0x10 //enable passthrough +#define DBGV_UNSET_PASSTHROUGH CTL_CODE(FILE_DEVICE_DBGV, DWORD(5), METHOD_BUFFERED, FILE_ANY_ACCESS) //0x14 // +#define DBGV_IS_DRIVER_AVAILABLE CTL_CODE(FILE_DEVICE_DBGV, DWORD(8), METHOD_BUFFERED, FILE_ANY_ACCESS) //0x20 //test driver is valid or functional +#define DBGV_GET_DRIVER_VERSION CTL_CODE(FILE_DEVICE_DBGV, DWORD(9), METHOD_BUFFERED, FILE_ANY_ACCESS) //0x24 //driver version, 4.70 = 0x800 +#define DBGV_SET_CARRIAGE_RETURN CTL_CODE(FILE_DEVICE_DBGV, DWORD(0x0d), METHOD_BUFFERED, FILE_ANY_ACCESS) //0x34 //force carriage return +#define DBGV_UNSET_CARRIAGE_RETURN CTL_CODE(FILE_DEVICE_DBGV, DWORD(0x0e), METHOD_BUFFERED, FILE_ANY_ACCESS) //0x38 // +#define DBGV_SET_VERBOSE_MESSAGES CTL_CODE(FILE_DEVICE_DBGV, DWORD(0x0f), METHOD_BUFFERED, FILE_ANY_ACCESS) //0x3C //enable log verbose +#define DBGV_UNSET_VERBOSE_MESSAGES CTL_CODE(FILE_DEVICE_DBGV, DWORD(0x10), METHOD_BUFFERED, FILE_ANY_ACCESS) //0x40 //reset log verbose +// clang-format on + +inline std::string feature_to_string(DWORD value) +{ + switch (value) + { + case DBGV_CAPTURE_KERNEL: return "DBGV_CAPTURE_KERNEL"; + case DBGV_UNCAPTURE_KERNEL: return "DBGV_UNCAPTURE_KERNEL"; + case DBGV_CLEAR_DISPLAY: return "DBGV_CLEAR_DISPLAY"; + case DBGV_READ_LOG: return "DBGV_READ_LOG"; + case DBGV_SET_PASSTHROUGH: return "DBGV_SET_PASSTHROUGH"; + case DBGV_UNSET_PASSTHROUGH: return "DBGV_UNSET_PASSTHROUGH"; + case DBGV_IS_DRIVER_AVAILABLE: return "DBGV_IS_DRIVER_AVAILABLE"; + case DBGV_GET_DRIVER_VERSION: return "DBGV_GET_DRIVER_VERSION"; + case DBGV_SET_CARRIAGE_RETURN: return "DBGV_SET_CARRIAGE_RETURN"; + case DBGV_UNSET_CARRIAGE_RETURN: return "DBGV_UNSET_CARRIAGE_RETURN"; + case DBGV_SET_VERBOSE_MESSAGES: return "DBGV_SET_VERBOSE_MESSAGES"; + case DBGV_UNSET_VERBOSE_MESSAGES: return "DBGV_UNSET_VERBOSE_MESSAGES"; + default: break; + } + return std::to_string(value); +} + +// suppress the non-standard use of zero-sized array in struct/union +#pragma warning(disable : 4200) + +#pragma pack(1) +typedef struct +{ + DWORD dwIndex; + FILETIME liSystemTime; + LARGE_INTEGER liPerfCounter; + CHAR strData[0]; +} LOG_ITEM, *PLOG_ITEM; +#pragma pack() + +constexpr const wchar_t* strDbgviewKernelDriverDeviceName = L"\\\\.\\dbgv"; +constexpr const DWORD kernelMessageBufferSize = 0x10000; + +void InstallKernelMessagesDriver(const std::string& driverLocation); +void UninstallKernelMessagesDriver(); diff --git a/application/include/DebugViewppLib/FileReader.h b/application/include/DebugViewppLib/FileReader.h index cf220940..3f5e6bef 100644 --- a/application/include/DebugViewppLib/FileReader.h +++ b/application/include/DebugViewppLib/FileReader.h @@ -30,6 +30,7 @@ class FileReader : public LogSource HANDLE GetHandle() const override; void Notify() override; void PreProcess(Line& line) const override; + virtual bool GetAutoNewLine() const override; protected: virtual void AddLine(const std::string& line); diff --git a/application/include/DebugViewppLib/Filter.h b/application/include/DebugViewppLib/Filter.h index c1501a64..f91462f5 100644 --- a/application/include/DebugViewppLib/Filter.h +++ b/application/include/DebugViewppLib/Filter.h @@ -15,8 +15,6 @@ #include #include -#pragma comment(lib, "DebugView++Lib.lib") - namespace fusion { namespace debugviewpp { diff --git a/application/include/DebugViewppLib/KernelReader.h b/application/include/DebugViewppLib/KernelReader.h new file mode 100644 index 00000000..59e4d791 --- /dev/null +++ b/application/include/DebugViewppLib/KernelReader.h @@ -0,0 +1,45 @@ +// (C) Copyright Gert-Jan de Vos and Jan Wilmans 2013. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#pragma once + +#include "PipeReader.h" +#include "PolledLogSource.h" + +#include "Win32/Win32Lib.h" + +#include "Debugview_kernel_client.h" + +namespace fusion { +namespace debugviewpp { + +std::string GetDebugviewDriverLocation(); + +class ILineBuffer; + +class KernelReader : public PolledLogSource +{ +public: + KernelReader(Timer& timer, ILineBuffer& linebuffer); + ~KernelReader() override; + void Abort() override; + bool AtEnd() const override; + + void SetVerbose(bool value); + void SetPassThrough(bool value); + +private: + void Poll() override; + + void StartListening(); + void StopListening(); + void SetKernelMessagesDriverFeature(DWORD); + + Win32::Handle m_handle; + PLOG_ITEM m_pBuf; +}; + +} // namespace debugviewpp +} // namespace fusion diff --git a/application/include/DebugViewppLib/LogSource.h b/application/include/DebugViewppLib/LogSource.h index 611721b5..9d6604ea 100644 --- a/application/include/DebugViewppLib/LogSource.h +++ b/application/include/DebugViewppLib/LogSource.h @@ -37,7 +37,7 @@ class LogSource : public fusion::noncopyable // return a handle to wait for, Notify() is called when the handle is signaled. return INVALID_HANDLE_VALUE if the Logsource does not need to be notified virtual HANDLE GetHandle() const = 0; - // only when nofity is called LogSource::Add may be used to add lines to the LineBuffer + // only when notify is called LogSource::Add may be used to add lines to the LineBuffer virtual void Notify() = 0; // called for each line before it is added to the view, @@ -64,6 +64,9 @@ class LogSource : public fusion::noncopyable // used by FileReader void Add(const std::string& message); + // get the relative time since the start of the session given the QWORD of the performance counter + double GetTimeSinceOrigin(long long ticks) const; + private: bool m_autoNewLine = true; ILineBuffer& m_linebuffer; diff --git a/application/include/DebugViewppLib/LogSources.h b/application/include/DebugViewppLib/LogSources.h index 8a94b76b..6e4e34e9 100644 --- a/application/include/DebugViewppLib/LogSources.h +++ b/application/include/DebugViewppLib/LogSources.h @@ -15,12 +15,11 @@ #include "DebugviewppLib/ProcessMonitor.h" #include "CobaltFusion/Throttle.h" -#pragma comment(lib, "DebugView++Lib.lib") - namespace fusion { namespace debugviewpp { class DBWinReader; +class KernelReader; class ProcessReader; class FileReader; class AnyFileReader; @@ -75,6 +74,7 @@ class LogSources void CallSources(std::function predicate) const; DBWinReader* AddDBWinReader(bool global); + KernelReader* AddKernelReader(); ProcessReader* AddProcessReader(const std::wstring& pathName, const std::wstring& args); BinaryFileReader* AddBinaryFileReader(const std::wstring& filename); AnyFileReader* AddAnyFileReader(const std::wstring& filename, bool keeptailing); diff --git a/application/include/DebugViewppLib/PolledLogSource.h b/application/include/DebugViewppLib/PolledLogSource.h index 7a4e38a3..83bad940 100644 --- a/application/include/DebugViewppLib/PolledLogSource.h +++ b/application/include/DebugViewppLib/PolledLogSource.h @@ -20,7 +20,7 @@ struct PollLine PollLine(DWORD pid, const std::string& processName, const std::string& message, const LogSource* pLogSource); PollLine(double time, FILETIME systemTime, DWORD pid, const std::string& processName, const std::string& message, const LogSource* pLogSource); - bool timesValid; + bool timesAreValid; // indicated 'time' and 'systemTime' were assigned values at construction double time; FILETIME systemTime; Win32::Handle handle; @@ -41,7 +41,7 @@ class PolledLogSource : public LogSource virtual void Poll(); void Abort() override; - // in contrast to the LogSource::Add methdods, these methods are de-coupled using m_backBuffer so they + // in contrast to the LogSource::Add methods, these methods are de-coupled using m_backBuffer so they // can be used to add messages from any thread. The typical use-case are messages from the UI thread. void AddMessage(Win32::Handle handle, const std::string& message); void AddMessage(DWORD pid, const std::string& processName, const std::string& message); diff --git a/application/include/DebugViewppLib/ProcessInfo.h b/application/include/DebugViewppLib/ProcessInfo.h index eba6ebe0..a8dc0363 100644 --- a/application/include/DebugViewppLib/ProcessInfo.h +++ b/application/include/DebugViewppLib/ProcessInfo.h @@ -10,8 +10,6 @@ #include "windows.h" -#pragma comment(lib, "DebugView++Lib.lib") - namespace fusion { namespace debugviewpp { diff --git a/application/include/IndexedStorageLib/IndexedStorage.h b/application/include/IndexedStorageLib/IndexedStorage.h index f2a1fc06..588ffa56 100644 --- a/application/include/IndexedStorageLib/IndexedStorage.h +++ b/application/include/IndexedStorageLib/IndexedStorage.h @@ -9,8 +9,6 @@ #include #include -#pragma comment(lib, "IndexedStorageLib.lib") - namespace fusion { namespace indexedstorage { diff --git a/build.bat b/build.bat index 882d9d9b..b8f4de92 100644 --- a/build.bat +++ b/build.bat @@ -1,2 +1,5 @@ +:: if the nuget package configuration fails, make sure +:: Tools -> Options -> NuGet Package Manager -> Package Sources contains 'https://api.nuget.org/v3/index.json.' + cmake -DCMAKE_INSTALL_PREFIX=install -G "Visual Studio 17 2022" -B vs2022 . start "" vs2022\debugviewpp.sln diff --git a/build_with_ninja.bat b/build_with_ninja.bat index 7e894298..54fd96f8 100644 --- a/build_with_ninja.bat +++ b/build_with_ninja.bat @@ -1,2 +1,6 @@ +:: building with ninja is faster (~20 seconds) compared to building with the "Visual Studio 17 2022" generator (~26 seconds) +:: however, our CI (Appveyor) works well with visual studio solution files, so we provide a way to create both. + +set "NINJA_STATUS=[%%e %%f/%%t @%%r] " cmake -DCMAKE_INSTALL_PREFIX=install -DCMAKE_BUILD_TYPE=Release -G Ninja -B build . ninja -C build diff --git a/doc/DesignOverview.md b/doc/DesignOverview.md index 148e2488..656eb438 100644 --- a/doc/DesignOverview.md +++ b/doc/DesignOverview.md @@ -1,7 +1,7 @@ Project dependencies -------------------- -![DebugView++ Projects](art/DebugViewProjects.png "DebugView++ Projects") +![DebugView++ Projects](../art/DebugViewProjects.png "DebugView++ Projects") 1. Project **DebugViewCmd** CommandLine version of debugview++, diff --git a/utils/apply-clang-format.py b/utils/apply-clang-format.py index 01ace570..4928d64f 100644 --- a/utils/apply-clang-format.py +++ b/utils/apply-clang-format.py @@ -30,12 +30,20 @@ def IsCppFile(fullname): return True if ext.lower().endswith(".hh"): return True - return False + return False def IsIgnoredDirectory(dirname): - if dirname.lower().endswith("\gen"): + if dirname.lower().endswith("\\gen"): + return True + if dirname.lower().endswith("\\gen64"): + return True + if dirname.lower().endswith("\\nuget"): + return True + if dirname.lower().endswith("\\packages"): return True - if dirname.lower().endswith("\gen64"): + if "\\application\\Libraries\\" in dirname.lower(): + return True + if "\\nuget\\packages\\" in dirname.lower(): return True return False @@ -44,22 +52,22 @@ def GetFiles(filemask, recurse): result = [] path = os.path.dirname(os.path.abspath(filemask)) for dirName, subdirList, fileList in os.walk(path): - if IsIgnoredDirectory(dirName): + if IsIgnoredDirectory(dirName): continue for fname in fileList: filename = dirName + "\\" + fname - if not IsCppFile(filename): + if not IsCppFile(filename): continue result += [filename] return result else: result = [] for filename in glob.glob(filemask): - if not IsCppFile(filename): + if not IsCppFile(filename): continue result += [os.path.abspath(filename)] return result - + def filter(keyword): for line in sys.stdin: if not keyword in line: @@ -80,12 +88,12 @@ def HasInvalidArgs(): if sys.argv[1].startswith("/"): return True return False - + def main(): if HasInvalidArgs(): print ("Usage: " + os.path.basename(sys.argv[0]) + " [/s] [/f]") print (" note: gen\ and gen64\ directories are ignored") - print (" /s = recursively process files in subdirecties"); + print (" /s = recursively process files in subdirectories"); print (" /f = actually format the selected files instead of just listing them"); print ("") print (" example: ") @@ -105,7 +113,7 @@ def main(): except KeyboardInterrupt: pass except Exception: - traceback.print_exc(file=sys.stdout) + traceback.print_exc(file=sys.stdout) sys.exit(0) - + diff --git a/utils/upx.exe b/utils/upx.exe index 50aac926..f092ae5b 100644 Binary files a/utils/upx.exe and b/utils/upx.exe differ