diff --git a/.github/workflows/ci-cell.yml b/.github/workflows/ci-cell.yml index 2cf3270..e8c9e48 100644 --- a/.github/workflows/ci-cell.yml +++ b/.github/workflows/ci-cell.yml @@ -39,6 +39,12 @@ jobs: mingw-w64-x86_64-cmake mingw-w64-x86_64-make + - name: Checkout STLSoft + shell: bash + run: | + set -euo pipefail + git clone --depth 1 https://github.com/synesissoftware/STLSoft "$RUNNER_TEMP/stlsoft" + - name: Configure shell: bash run: | @@ -49,9 +55,11 @@ jobs: -DCMAKE_BUILD_TYPE="${{ inputs.build-type }}" \ -DCMAKE_C_COMPILER=gcc \ -DCMAKE_CXX_COMPILER=g++ \ + -DSTLSOFT="$RUNNER_TEMP/stlsoft" \ -DBUILD_TESTING=OFF else cmake -B build -S . \ + -DSTLSOFT="$RUNNER_TEMP/stlsoft" \ -DBUILD_TESTING=OFF fi @@ -86,4 +94,4 @@ jobs: set -euo pipefail PREFIX="${RUNNER_TEMP}/install-prefix" PROJECT="$(tr -d '[:space:]' < .sis/project_name.txt)" - "$PREFIX/bin/${PROJECT}.exe" + "$PREFIX/bin/${PROJECT}.exe" --version diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c83ed53 --- /dev/null +++ b/.gitignore @@ -0,0 +1,151 @@ + +# directories (by name) + +/.svn/ +/.vs/ + +/_build/ +/_internal/ + +/bin/ +/include/ +/lib/ +/node_modules/ + +/include/b64/ +/include/shwild/ +/include/xcontract/ +/include/xcover/ +/include/xtests/ + +/src/b64/ +/src/shwild/ +/src/xcontract/ +/src/xcover/ +/src/xtests/ + +Debug Multithreaded DLL/ +Debug Multithreaded Dll/ +Debug Multithreaded pseudoUNIX/ +Debug Multithreaded pseudoUnix/ +Debug Multithreaded/ +Debug/ +DebugDLLUNIX/ +DebugDll/ +DebugMTDLL/ +DebugMt/ +DebugNox/ +DebugUNIX/ +DebugUnix/ +Release Multithreaded DLL/ +Release Multithreaded Dll/ +Release Multithreaded pseudoUNIX/ +Release Multithreaded pseudoUnix/ +Release Multithreaded/ +Release/ +ReleaseDLLUNIX/ +ReleaseDll/ +ReleaseMTDLL/ +ReleaseMt/ +ReleaseNox/ +ReleaseUnix/ +UDebug/ +UDebugMt/ +URelease/ +UReleaseMt/ +Unicode Debug Multithreaded DLL/ +Unicode Debug Multithreaded Dll/ +Unicode Debug Multithreaded/ +Unicode Debug/ +Unicode Release Multithreaded DLL/ +Unicode Release Multithreaded Dll/ +Unicode Release Multithreaded/ +Unicode Release/ + +Win32/ +ipch/ +x64/ + + +# directories (by pattern) + + +# files (by name) + +.DS_Store +.ruby-version +.svnignore +.svnignore-at-root +.update_deps + +list_changes.cmd +logging-bailout.txt +prepare.cmd +sh.exe.stackdump +update_deps.cmd + +build/makefile.tmpl +build/makefile.tools.xml + +ReadDebugString +ReadDebugString_test + + +# files (by pattern) + +RCa0* +RCb0* + +*~ +*.???_obj +*.a +*.app +*.aps +*.b64 +*.bak +*.chm +*.class +*.csproj*user +*.d +*.dll +*.dylib +*.exe +*.gch +*.gem +*.idb +*.ilk +*.iobj +*.ipch +*.ipdb +*.la +*.lai +*.lib +*.lo +*.log +*.ncb +*.o +*.obj +*.opensdf +*.opt +*.org +*.out +*.pch +*.pdb +*.pyc +*.rar +*.res +*.sbr +*.scc +*.sdf +*.slo +*.so +*.suo +*.swp +*.tlog +*.tmp +*.unsuccessfulbuild +*.vcproj*user +*.vcxproj*user +*.xcuserstate +*.zip + diff --git a/.sis/script_info_lines.txt b/.sis/script_info_lines.txt index 8129a6d..79719eb 100644 --- a/.sis/script_info_lines.txt +++ b/.sis/script_info_lines.txt @@ -1,2 +1,2 @@ ReadDebugString is a Windows CLI that reads messages from the Windows debugger -Copyright (c) 2026, Matthew Wilson and Synesis Information Systems +Copyright (c) 2025-2026, Matthew Wilson and Synesis Information Systems diff --git a/AUTHORS.md b/AUTHORS.md new file mode 100644 index 0000000..b5b0893 --- /dev/null +++ b/AUTHORS.md @@ -0,0 +1,17 @@ +# ReadDebugString - Authors + + +## Major Contributors + +* Matthew Wilson ([mwsis](https://github.com/mwsis)); + + +## Defect reports, fixes and suggestions (for which we are very grateful) + +* \ (yet); + + +Contributions are welcomed. + + + diff --git a/CHANGES.md b/CHANGES.md new file mode 100644 index 0000000..89d581a --- /dev/null +++ b/CHANGES.md @@ -0,0 +1,23 @@ +# ReadDebugString - Changes + + +## 0.0.1 - 24th August 2026 + +* Replaced the initial executable scaffold with a Windows debugger message reader; +* Added process identification to reader output; +* Improved error handling for Windows file-mapping and debugger access failures; +* Improved CMake flexibility for uninstalled **STLSoft** source trees supplied via **STLSOFT**; +* Added validation and an imported **STLSoft::STLSoft** target for local STLSoft source trees; +* Added Windows **cl** and MinGW CI configuration with STLSoft source checkout; +* Added an installation smoke test using **--version** so the long-running reader is not started; +* Added a section-1 reference page and installed it with CMake; + + +## 0.0.0 - 16th August 2026 + +* Added the initial Windows-only **ReadDebugString** executable scaffold; +* Added **--help** and **--version** command-line handling; +* Added CMake configuration, helper scripts, and editor settings; + + + diff --git a/CMakeLists.txt b/CMakeLists.txt index 796fb29..31e2ec7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,36 +1,265 @@ +# ######################################################################## # +# File: /CMakeLists.txt +# +# Purpose: Top-level CMake lists file for ReadDebugString +# +# Created: 15th August 2025 +# Updated: 24th August 2026 +# +# ######################################################################## # + + +# ########################################################## +# CMake + cmake_minimum_required(VERSION 3.20 FATAL_ERROR) +# require out-of-source builds +file(TO_CMAKE_PATH "${CMAKE_CURRENT_BINARY_DIR}/CMakeLists.txt" LOC_PATH) +if(EXISTS "${LOC_PATH}") + + message(FATAL_ERROR "You cannot build in a source directory (or any directory with a CMakeLists.txt file). Please make a build subdirectory. Feel free to remove CMakeCache.txt and CMakeFiles.") +endif() + +# directory for CMake specific extensions and source files. +set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH}) + + +# ########################################################## +# project + project(ReadDebugString - VERSION 0.0.1 - DESCRIPTION "ReadDebugString is a Windows CLI that reads messages from the Windows debugger." - HOMEPAGE_URL "https://github.com/sistools/ReadDebugString" - LANGUAGES C + DESCRIPTION "ReadDebugString is a Windows CLI that reads messages from the Windows debugger." + HOMEPAGE_URL "https://github.com/sistools/ReadDebugString" + LANGUAGES C CXX ) -if(NOT WIN32) - message(FATAL_ERROR "${PROJECT_NAME} targets Windows only") -endif() +string(TOLOWER ${PROJECT_NAME} PROJECT_NAME_LOWER) +string(TOUPPER ${PROJECT_NAME} PROJECT_NAME_UPPER) +# handle version number +set(RX_PROJ_TAG "${PROJECT_NAME_UPPER}") +set(RX_WS "[ \t]") +file(READ "${CMAKE_SOURCE_DIR}/main.cpp" _impl_file) +string(REGEX MATCH "#${RX_WS}*define${RX_WS}+_?${RX_PROJ_TAG}_VER_MAJOR${RX_WS}+([0-9]+)" MAJOR_DUMMY ${_impl_file}) +set(_VERSION_MAJOR ${CMAKE_MATCH_1}) +string(REGEX MATCH "#${RX_WS}*define${RX_WS}+_?${RX_PROJ_TAG}_VER_MINOR${RX_WS}+([0-9]+)" MINOR_DUMMY ${_impl_file}) +set(_VERSION_MINOR ${CMAKE_MATCH_1}) +string(REGEX MATCH "#${RX_WS}*define${RX_WS}+_?${RX_PROJ_TAG}_VER_PATCH${RX_WS}+([0-9]+)" PATCH_DUMMY ${_impl_file}) +set(_VERSION_PATCH ${CMAKE_MATCH_1}) + +# set project version number here +set(PROJECT_VERSION_MAJOR ${_VERSION_MAJOR}) +set(PROJECT_VERSION_MINOR ${_VERSION_MINOR}) +set(PROJECT_VERSION_PATCH ${_VERSION_PATCH}) +set(PROJECT_VERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}") + +# adhere strictly to C and C++ standards plus extensions. These are actually +# useless since we do not compile anything; they merely state our intention. set(CMAKE_C_STANDARD 17) set(CMAKE_C_STANDARD_REQUIRED ON) -set(CMAKE_C_EXTENSIONS ON) +set(CMAKE_C_EXTENSIONS ON) # GNU extensions and POSIX standard +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS ON) + +if(MSVC) + + if(MSVC_VERSION GREATER_EQUAL 1914) + + add_compile_options("/Zc:__cplusplus") + + add_definitions(-D_SILENCE_CXX17_ITERATOR_BASE_CLASS_DEPRECATION_WARNING) + endif() + + if(MSVC_VERSION LESS 1930) + + set(CMAKE_C_STANDARD 90) + set(CMAKE_CXX_STANDARD 98) + endif() + + if(MSVC_USE_MT) + + set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + endif(MSVC_USE_MT) +else(MSVC) + + if(MSVC_USE_MT) + + # this here just to absorb warning about not using `MSVC_USE_MT` (to + # enable **prepare_cmake.sh** to be simple) + endif(MSVC_USE_MT) +endif(MSVC) + + +# ########################################################## +# dependencies, includes, options + +# ################################################ +# includes - 1 + +if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/cmake/BuildType.cmake") + + include(BuildType) +endif() +#[====[ +include(LanguageFullVersion) +include(TargetMacros) +#]====] + + +# ################################################ +# dependencies, features, and options + + +# ###################################### +# options + +option(BUILD_EXAMPLES "Build examples" ON) + +option(BUILD_TESTING "Build tests" ON) + + +# ###################################### +# features + +# This tool targets Windows only (WinSTL). +if(NOT WIN32) + + message(FATAL_ERROR "${PROJECT_NAME} targets Windows only") +endif() + + +# ###################################### +# dependencies +# +# required: +# - STLSoft; +# +# required if testing enabled: +# - (none); +# +# optional: + +# ############################ +# STLSoft +# +# NOTE: This is resolved before other dependencies because imported targets +# may reference STLSoft::STLSoft in their link interfaces. + +if(DEFINED STLSOFT) + + message("-- STLSOFT provided as CMake variable with value '${STLSOFT}'") + set(STLSOFT_INCLUDE_DIR "${STLSOFT}/include") +elseif(DEFINED ENV{STLSOFT}) + + message("-- STLSOFT provided as environment variable with value '$ENV{STLSOFT}'") + set(STLSOFT_INCLUDE_DIR "$ENV{STLSOFT}/include") +endif() + +if(DEFINED STLSOFT_INCLUDE_DIR) + + if(NOT IS_DIRECTORY "${STLSOFT_INCLUDE_DIR}") + + message(FATAL_ERROR "STLSoft include directory not found: ${STLSOFT_INCLUDE_DIR}") + endif() + + if(NOT TARGET STLSoft::STLSoft) + + add_library(STLSoft::STLSoft INTERFACE IMPORTED GLOBAL) + set_target_properties(STLSoft::STLSoft PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${STLSOFT_INCLUDE_DIR}" + ) + endif() +else() + + set(STLSoft_REQUIRED_VERSION_ 1.11.1) + + find_package(STLSoft ${STLSoft_REQUIRED_VERSION_} REQUIRED) + + message("-- CMake package STLSoft found (version ${STLSoft_VERSION}; ${STLSoft_REQUIRED_VERSION_} requested)") +endif() + +if(DEFINED STLSOFT_INCLUDE_DIR) + + include_directories("${STLSOFT_INCLUDE_DIR}") +endif() + + +# ################################################ +# includes - 2 + +include(CMakePackageConfigHelpers) +if(BUILD_TESTING) + + include(CTest) +endif(BUILD_TESTING) include(GNUInstallDirs) + +# ########################################################## +# tool + add_executable(${PROJECT_NAME} - main.c + main.cpp ) target_compile_options(${PROJECT_NAME} - PRIVATE - $<$: - -Werror -Wall -Wextra -pedantic - > - $<$: - /WX /W4 - > + PRIVATE + $<$,$,$>: + -Werror -Wall -Wextra -pedantic + > + $<$,$>: + -Wno-anonymous-structs + > + $<$: + /WX /W4 + + /wd4201 + /wd4702 + > +) + +target_link_options(${PROJECT_NAME} + PRIVATE + $<$:-municode> +) + +target_link_libraries(${PROJECT_NAME} + $<$:STLSoft::STLSoft> ) install(TARGETS ${PROJECT_NAME} - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} +) + +install(FILES + doc/${PROJECT_NAME_LOWER}.1 + DESTINATION ${CMAKE_INSTALL_MANDIR}/man1 ) + + +# ################################################ +# examples + + +# ################################################ +# tests + +if(BUILD_TESTING) + + message("-- enabled building of tests ...") +else(BUILD_TESTING) + + message("-- disabled building of tests - define BUILD_TESTING to enable") +endif(BUILD_TESTING) + + +# ########################################################## +# completion + +message(NOTICE "Generating CMake build scripts for ${PROJECT_NAME} ${PROJECT_VERSION}, for C${CMAKE_C_STANDARD} C++${CMAKE_CXX_STANDARD}") + + +# ############################## end of file ############################# # diff --git a/INSTALL.md b/INSTALL.md new file mode 100644 index 0000000..539479d --- /dev/null +++ b/INSTALL.md @@ -0,0 +1,41 @@ +# ReadDebugString - Installation and Use + + +## Building + +The primary build method is **CMake**. From a Visual Studio developer +environment: + +```powershell +cmake -S . -B _build +cmake --build _build --config Release +``` + +If **STLSoft** is not installed as a CMake package, provide its source-tree +root: + +```powershell +cmake -S . -B _build -DSTLSOFT=C:\path\to\STLSoft +cmake --build _build --config Release +``` + +The resulting executable is installed with: + +```powershell +cmake --install _build --config Release +``` + + +## Command-line use + +The program is intended to remain running while it reads messages from the +Windows debugger. Use `--help` or `--version` for commands that terminate +immediately: + +```powershell +ReadDebugString.exe --help +ReadDebugString.exe --version +``` + + + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..95c9118 --- /dev/null +++ b/Makefile @@ -0,0 +1,29 @@ + +!ifndef STLSOFT +!error Environment variable STLSOFT is not defined +!endif # STLSOFT + +CC=cl.exe +LINK=link.exe + +CFLAGS=/EHsc /Zi /nologo /std:c++17 /Zc:__cplusplus /I "$(STLSOFT)/include" + +LDFLAGS=/NOLOGO + +SRCS=main.cpp + +OBJS=$(SRCS:.cpp=.obj) + +all: build + +build: ReadDebugString.exe + +ReadDebugString.exe: main.obj + $(LINK) $(LDFLAGS) /OUT:$@ $(OBJS) + +.cpp.obj: + $(CC) $(CFLAGS) /c $< + +clean: + @del /Q *.obj *.exe + diff --git a/NEWS.md b/NEWS.md new file mode 100644 index 0000000..62441bb --- /dev/null +++ b/NEWS.md @@ -0,0 +1,11 @@ +# ReadDebugString - News + + +| Date | News Item | +| ------------------- | --------- | +| 24th August 2026 | [0.0.1 released](https://github.com/sistools/ReadDebugString/releases/tag/0.0.1) | +| 16th August 2026 | [0.0.0 released](https://github.com/sistools/ReadDebugString/releases/tag/0.0.0) | +| 16th August 2026 | [0.0.0 released](https://github.com/sistools/ReadDebugString/releases/tag/0.0.0) | + + + diff --git a/README.md b/README.md index ad74804..1d2600d 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ Reads messages from the Windows debugger. -![C](https://img.shields.io/badge/C-00599C?style=flat&logo=c&logoColor=white) +![C++](https://img.shields.io/badge/C%2B%2B-00599C?style=flat&logo=cplusplus&logoColor=white) ![Windows](https://img.shields.io/badge/OS-Windows-0078D6?style=flat&logo=windows&logoColor=white) [![License](https://img.shields.io/badge/License-BSD_3--Clause-blue.svg)](https://opensource.org/licenses/BSD-3-Clause) [![GitHub release](https://img.shields.io/github/v/release/sistools/ReadDebugString.svg)](https://github.com/sistools/ReadDebugString/releases/latest) @@ -27,10 +27,8 @@ Reads messages from the Windows debugger. ## Introduction **ReadDebugString** is a small Windows-only utility that reads messages from -the Windows debugger. - -The current implementation is a minimal buildable scaffold. Its debugger -message-reading behaviour is still being developed. +the Windows debugger, identifies the originating process, and writes formatted +messages to standard error. ## Installation @@ -42,6 +40,8 @@ cmake -S . -B _build cmake --build _build --config Release ``` +Detailed installation and usage instructions are in [INSTALL.md](./INSTALL.md). + ## Components @@ -50,8 +50,8 @@ The project creates a single executable program, **ReadDebugString**. ## Examples -The current scaffold does not yet expose command-line options or produce -debugger output. +With no arguments, the program waits for and displays messages from the Windows +debugger. Use `--help` or `--version` for commands that terminate immediately. ## Project Information diff --git a/REQUISITES.md b/REQUISITES.md new file mode 100644 index 0000000..bbabb58 --- /dev/null +++ b/REQUISITES.md @@ -0,0 +1,26 @@ +# ReadDebugString - Requisites + + +## Introduction + +Building **ReadDebugString** requires **CMake** 3.20 or later and a Windows +C++ toolchain supporting C++17. + + +## Required dependencies + +**ReadDebugString** depends on: + +* [**STLSoft**](https://github.com/synesissoftware/STLSoft) 1.11.1 or later; + +An installed **STLSoft** CMake package is used by default. An uninstalled +source tree may be supplied through the **STLSOFT** CMake variable, the +**STLSOFT** environment variable, or **prepare_cmake.sh --stlsoft-root-dir**. + + +## Operating system + +The program targets Microsoft Windows and uses the Windows debugger APIs. + + + diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..e69de29 diff --git a/build_cmake.sh b/build_cmake.sh index 3f261e7..6b96a40 100755 --- a/build_cmake.sh +++ b/build_cmake.sh @@ -8,7 +8,6 @@ CMakeDir=${SIS_CMAKE_BUILD_DIR:-$Dir/_build} if [[ -n "$MSYSTEM" ]]; then DefaultMakeCmd=mingw32-make.exe - MinGW=1 else DefaultMakeCmd=make @@ -24,7 +23,7 @@ Targets=() # ########################################################## # colours -if command -v tput > /dev/null; then +if [ -n "${TERM:-}" ] && [ -t 1 ] && command -v tput >/dev/null 2>&1; then SisClr_Blue=${FG_BLUE:-$(tput setaf 4)} SisClr_Red=${FG_RED:-$(tput setaf 1)} diff --git a/clean_cmake.sh b/clean_cmake.sh index 96b6c07..17d36a3 100755 --- a/clean_cmake.sh +++ b/clean_cmake.sh @@ -8,7 +8,6 @@ CMakeDir=${SIS_CMAKE_BUILD_DIR:-$Dir/_build} if [[ -n "$MSYSTEM" ]]; then DefaultMakeCmd=mingw32-make.exe - MinGW=1 else DefaultMakeCmd=make @@ -21,7 +20,7 @@ ProjectName=$(tr -d '[:space:]' < "$ProjectNameFile") # ########################################################## # colours -if command -v tput > /dev/null; then +if [ -n "${TERM:-}" ] && [ -t 1 ] && command -v tput >/dev/null 2>&1; then SisClr_Blue=${FG_BLUE:-$(tput setaf 4)} SisClr_Red=${FG_RED:-$(tput setaf 1)} diff --git a/cmake/BuildType.cmake b/cmake/BuildType.cmake new file mode 100644 index 0000000..a315b8d --- /dev/null +++ b/cmake/BuildType.cmake @@ -0,0 +1,59 @@ + +# ######################################################################## # +# File: /cmake/BuildType.cmake +# +# Purpose: CMake module file (for BuildType) +# +# Created: 16th October 2019 +# Updated: 25th October 2024 +# +# ######################################################################## # + + +# Including this module sets the `CMAKE_BUILD_TYPE` value as follows: +# +# 1. If user specifies on the command line, then `CMAKE_BUILD_TYPE` already +# has a valid, so this is accepted; +# 2. If the top-level .git directory is specified, then "Debug" is used; +# otherwise +# 3. "Release" is used. +# +# Note: +# - `CMAKE_BUILD_TYPE` is the build (configuration) type; +# - `CMAKE_CONFIGURATION_TYPES` is, if specified, a list of valid +# configurations (see +# https://cmake.org/cmake/help/latest/variable/CMAKE_CONFIGURATION_TYPES.html); +# +# Example usage: +# +#[========[ + +# CMakeLists.txt + +include(BuildType) + +]========] + + +if(EXISTS "${CMAKE_SOURCE_DIR}/.git") + set(DEFAULT_BUILD_TYPE "Debug") +else() + set(DEFAULT_BUILD_TYPE "Release") +endif() + +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + + message(STATUS "Setting build type to '${DEFAULT_BUILD_TYPE}' as none was specified.") + + set(CACHE CMAKE_BUILD_TYPE "${DEFAULT_BUILD_TYPE}" + STRING "Choose the type of build." FORCE + ) + + # Set the possible values of build type for cmake-gui + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Release" "MinSizeRel" "RelWithDebInfo" + ) +endif() + +# ############################## end of file ############################# # + diff --git a/doc/readdebugstring.1 b/doc/readdebugstring.1 new file mode 100644 index 0000000..d261e4f --- /dev/null +++ b/doc/readdebugstring.1 @@ -0,0 +1,37 @@ +.\" Copyright (c) 2025-2026, Matthew Wilson and Synesis Information Systems +.\" +.TH READDEBUGSTRING 1 "August 2026" "readdebugstring 0.0.1" "User Commands" +.SH NAME +readdebugstring \- read messages from the Windows debug output stream +.SH SYNOPSIS +.B readdebugstring +.br +.B readdebugstring +.B \-\-help +.br +.B readdebugstring +.B \-\-version +.SH DESCRIPTION +.B readdebugstring +waits for messages sent to the Windows debug output stream and writes each +message to standard error. Output includes the originating process ID and +process name when available. +.PP +With no arguments, the program continues waiting for debugger messages until +it is terminated. This is a Windows-only command. +.SH OPTIONS +.TP +.B \-\-help +Display usage information and exit. +.TP +.B \-\-version +Display version information and exit. +.SH SEE ALSO +.BR WriteDebugString (1) +.SH REPORTING BUGS +Report defects and feature requests at +https://github.com/sistools/ReadDebugString. +.SH AUTHOR +Matthew Wilson and Synesis Information Systems +.br +Copyright (c) 2025\-2026, Matthew Wilson and Synesis Information Systems diff --git a/main.c b/main.c deleted file mode 100644 index 4b782de..0000000 --- a/main.c +++ /dev/null @@ -1,8 +0,0 @@ -#define READDEBUGSTRING_VER_MAJOR 0 -#define READDEBUGSTRING_VER_MINOR 0 -#define READDEBUGSTRING_VER_PATCH 1 - -int main(void) -{ - return 0; -} diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..17e2567 --- /dev/null +++ b/main.cpp @@ -0,0 +1,265 @@ + +#define _UNICODE +#define UNICODE + +#include + +#if _STLSOFT_VER < 0x010b01c3 +# error requires STLSoft v1.11.1-rc3 or later +#endif +#if __cplusplus < 201702L +# error requires C++17 or later +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + + +#define PROGRAM_VER_MAJOR 0 +#define PROGRAM_VER_MINOR 0 +#define PROGRAM_VER_PATCH 1 + + +union Payload +{ +#if defined(__GNUC__) && !defined(__clang__) + __extension__ +#endif + struct + { + DWORD pid; + CHAR content[1]; + }; + UCHAR bytes[4096]; +}; + +// [[noreturn]] +__declspec(noreturn) +void throw_( + DWORD le +, char const* msg +) +{ + using namespace winstl; + + if (ERROR_ACCESS_DENIED == le) + { + STLSOFT_THROW_X(access_exception(msg, le)); + } + else + { + STLSOFT_THROW_X(winstl_exception(msg, le)); + } +} + + +std::string GetProcessNameFromPid(DWORD pid) { + HANDLE hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid); + if (hProcess == NULL) { + return "Error: Could not open process"; + } + + char buffer[MAX_PATH]; + if (GetProcessImageFileNameA(hProcess, buffer, MAX_PATH) == 0) { + CloseHandle(hProcess); + return "Error: Could not get image file name"; + } + + CloseHandle(hProcess); + + std::string fullPath(buffer); + size_t lastSlash = fullPath.find_last_of("\\"); + std::string processName = (lastSlash == std::string::npos) ? fullPath : fullPath.substr(lastSlash + 1); + + return processName; +} + +// [[noreturn]] +__declspec(noreturn) +void run() +{ + winstl::event ev_buffer_ready(L"DBWIN_BUFFER_READY", false, false); + winstl::event ev_data_ready(L"DBWIN_DATA_READY", false, false); + + HANDLE const hFileMap = CreateFileMappingA(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, sizeof(Payload), "DBWIN_BUFFER"); + + if (NULL == hFileMap) + { + DWORD const le = ::GetLastError(); + + throw_(le, "failed to create file-mapping"); + } + + stlsoft::scoped_handle scoper_fm(hFileMap, ::CloseHandle); + + Payload* const payload = (Payload*)::MapViewOfFile(hFileMap, SECTION_MAP_READ, 0, 0, 0); + + if (NULL == payload) + { + DWORD const le = ::GetLastError(); + + throw_(le, "failed to map view"); + } + + HANDLE hStderr = GetStdHandle(STD_ERROR_HANDLE); + + for (;;) + { + ev_buffer_ready.set(); + + DWORD const wait = ::WaitForSingleObject(ev_data_ready.handle(), INFINITE); + + if (WAIT_OBJECT_0 != wait) + { + DWORD const le = ::GetLastError(); + + throw_(le, "failed to wait for buffer-ready"); + } + size_t len = strlen(payload->content); + + for ( ; 0 != len; --len) + { + if ('\r' != payload->content[len - 1] && + '\n' != payload->content[len - 1]) + { + break; + } + } + + if (len != 0) + { + using stlsoft::stlsoft_C_snprintf; + + char msg[4096]; + int r = stlsoft_C_snprintf(msg, "%lu - %s: %.*s", payload->pid, GetProcessNameFromPid(payload->pid).c_str(), (int)len, payload->content); + DWORD numWritten; + + if (r < 5) + { + ::WriteFile(hStderr, "could not prepare output line\r\n", 31, &numWritten, NULL); + } + else + { + if (r > int(STLSOFT_NUM_ELEMENTS(msg) - 3)) + { + msg[STLSOFT_NUM_ELEMENTS(msg) - 3] = '\r'; + msg[STLSOFT_NUM_ELEMENTS(msg) - 2] = '\n'; + msg[STLSOFT_NUM_ELEMENTS(msg) - 1] = '\0'; + } + else + { + msg[r++] = '\r'; + msg[r++] = '\n'; + msg[r] = '\0'; + } + + ::WriteFile(hStderr, msg, (DWORD)r, &numWritten, NULL); + } + } + } +} + + +int wmain(int argc, wchar_t* argv[]) +{ + stlsoft::string_slice_w_t const program_name = platformstl::get_executable_name_from_path(argv[0]); + + try + { + switch (argc) + { + case 2: + + if (0 == std::wcscmp(L"--help", argv[1])) + { + std::wcout + << L"USAGE: " + << program_name + << L" [ { --help | --version } ]" + << std::endl; + + return EXIT_SUCCESS; + } + else + if (0 == std::wcscmp(L"--version", argv[1])) + { + std::wcout + << program_name + << L" v" + << PROGRAM_VER_MAJOR + << L'.' + << PROGRAM_VER_MINOR + << L'.' + << PROGRAM_VER_PATCH + << std::endl; + + return EXIT_SUCCESS; + } + else + { + std::wcerr + << program_name + << L": unrecognised argument '" + << argv[1] + << L"'; use --help for usage" + << std::endl; + + return EXIT_FAILURE; + } + break; + case 1: + + run(); + +#ifndef NDEBUG + + fwprintf( + stderr + , L"%.*s: %s:%d: UNEXPECTED\n" + , int(program_name.len), program_name.ptr + , STLSOFT_STRINGIZE_w(__FILE__), __LINE__ + ); + + ::DebugBreak(); + + return EXIT_FAILURE; +#endif + default: + + std::wcerr + << program_name + << L": too many arguments; use --help for usage" + << std::endl; + + return EXIT_FAILURE; + } + } + catch (std::bad_alloc&) + { + fputws(L"out of memory\n", stderr); + } + catch (std::exception& x) + { + fwprintf( + stderr + , L"%.*s: process failed: %S\n" + , int(program_name.len), program_name.ptr + , x.what() + ); + } + + return EXIT_SUCCESS; +} + diff --git a/prepare_cmake.sh b/prepare_cmake.sh index 417cb1b..b98a74d 100755 --- a/prepare_cmake.sh +++ b/prepare_cmake.sh @@ -8,7 +8,6 @@ CMakeDir=${SIS_CMAKE_BUILD_DIR:-$Dir/_build} if [[ -n "$MSYSTEM" ]]; then DefaultMakeCmd=mingw32-make.exe - MinGW=1 else DefaultMakeCmd=make @@ -18,7 +17,7 @@ ProjectNameFile="$Dir/.sis/project_name.txt" ProjectName=$(tr -d '[:space:]' < "$ProjectNameFile") Configuration=Release -CStandard= +CxxStandard= MSVC_MT=0 MinGW="${MinGW:=0}" RunMake=0 @@ -30,7 +29,7 @@ VerboseMakefile=0 # ########################################################## # colours -if command -v tput > /dev/null; then +if [ -n "${TERM:-}" ] && [ -t 1 ] && command -v tput >/dev/null 2>&1; then SisClr_Blue=${FG_BLUE:-$(tput setaf 4)} SisClr_Red=${FG_RED:-$(tput setaf 1)} @@ -51,16 +50,16 @@ fi while [[ $# -gt 0 ]]; do case $1 in - --c-standard) + --cxx-standard) shift - CStandard=$1 - case $CStandard in - 99|11|17|23) + CxxStandard=$1 + case $CxxStandard in + 98|11|14|17|20|23) ;; *) - >&2 echo "$ScriptPath: ${SisClr_Red}${SisClr_Bold}invalid C standard '$CStandard'${SisClr_None}; expected 99, 11, 17, or 23" + >&2 echo "$ScriptPath: ${SisClr_Red}${SisClr_Bold}invalid C++ standard '$CxxStandard'${SisClr_None}; expected 98, 11, 14, 17, 20, or 23" exit 1 ;; @@ -173,7 +172,7 @@ cd $CMakeDir echo "Executing CMake for ${SisClr_Blue}${SisClr_Bold}${ProjectName}${SisClr_None} (in ${SisClr_Blue}${SisClr_Bold}${CMakeDir}${SisClr_None})" -if [ -z "$CStandard" ]; then CMakeCStandardVariable="" ; else CMakeCStandardVariable="-DCMAKE_C_STANDARD=$CStandard" ; fi +if [ -z "$CxxStandard" ]; then CMakeCStandardVariable="" ; else CMakeCStandardVariable="-DCMAKE_C_STANDARD=$CxxStandard" ; fi if [ $MSVC_MT -eq 0 ]; then CMakeMsvcMtFlag="OFF" ; else CMakeMsvcMtFlag="ON" ; fi if [ -z "$STLSoftDirGiven" ]; then CMakeSTLSoftVariable="" ; else CMakeSTLSoftVariable="-DSTLSOFT=$STLSoftDirGiven/" ; fi if [ $TestingDisabled -eq 0 ]; then CMakeBuildTestingFlag="ON" ; else CMakeBuildTestingFlag="OFF" ; fi diff --git a/remove_cmake_artefacts.sh b/remove_cmake_artefacts.sh index 992153f..5b4478e 100755 --- a/remove_cmake_artefacts.sh +++ b/remove_cmake_artefacts.sh @@ -30,7 +30,7 @@ Files=( # ########################################################## # colours -if command -v tput > /dev/null; then +if [ -n "${TERM:-}" ] && [ -t 1 ] && command -v tput >/dev/null 2>&1; then SisClr_Blue=${FG_BLUE:-$(tput setaf 4)} SisClr_Red=${FG_RED:-$(tput setaf 1)} diff --git a/run_all_unit_tests.sh b/run_all_unit_tests.sh index b05b671..55b53fc 100755 --- a/run_all_unit_tests.sh +++ b/run_all_unit_tests.sh @@ -8,7 +8,6 @@ CMakeDir=${SIS_CMAKE_BUILD_DIR:-$Dir/_build} if [[ -n "$MSYSTEM" ]]; then DefaultMakeCmd=mingw32-make.exe - MinGW=1 else DefaultMakeCmd=make @@ -25,7 +24,7 @@ Verbose=0 # ########################################################## # colours -if command -v tput > /dev/null; then +if [ -n "${TERM:-}" ] && [ -t 1 ] && command -v tput >/dev/null 2>&1; then SisClr_Blue=${FG_BLUE:-$(tput setaf 4)} SisClr_Red=${FG_RED:-$(tput setaf 1)}