Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] New debug dump functionality #2660

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ set(VERSION_PATCH "${VERSION_PATCH}-beta")
set(VERSION "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}")

set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
Copy link
Contributor

@akruphi akruphi Feb 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Всё-таки такая смена с 11 на 17 должна быть не постоянной, а только для CMAKE_BUILD_TYPE равное например DEBUGextend, т.е. даём разрабам возможность дебажить по старинке в стандарте 11 и по новому в стандарте 17 имея риск, что, если применены новомодности, то потом при сборке релиза в стандарте 11 они боком вылезут.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ага, это первая из моих хотелок в тикете. Надо делать и сборку дампера и общий режим C++17 отключаемыми вместе.

set(CMAKE_CXX_EXTENSIONS OFF)

if(NOT CMAKE_BUILD_TYPE)
Expand Down
6 changes: 3 additions & 3 deletions calc/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@ target_include_directories(${PROJECT_NAME} PRIVATE ../WinPort)
target_include_directories(${PROJECT_NAME} PRIVATE ../far2l/far2sdk)

set_target_properties(${PROJECT_NAME} PROPERTIES
CXX_STANDARD 11
CXX_STANDARD_REQUIRED YES
CXX_EXTENSIONS NO
# CXX_STANDARD 11
# CXX_STANDARD_REQUIRED YES
# CXX_EXTENSIONS NO
LIBRARY_OUTPUT_DIRECTORY "${INSTALL_DIR}/Plugins/${PROJECT_NAME}/plug"
PREFIX ""
SUFFIX ".far-plug-wide")
Expand Down
15 changes: 14 additions & 1 deletion far2l/src/base/FARString.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,7 @@ FARString& FARString::Copy(const char *lpszData, UINT CodePage)
WINPORT(MultiByteToWideChar)(CodePage, 0, lpszData, -1, m_pContent->GetData(), nSize);
m_pContent->SetLength(nSize - 1);

} else
} else
Init();
}

Expand Down Expand Up @@ -648,3 +648,16 @@ size_t FARString::TruncateByCells(size_t nCount)
Truncate(sz);
return ng;
}


// Definition of dump_value for FARString
#include "debug.h"
template <>
inline void dump_value(
std::ostringstream& oss,
std::string_view var_name,
const FARString& value)
{
std::string str_value = value.GetMB();
dump_value(oss, var_name, str_value);
}
159 changes: 159 additions & 0 deletions utils/include/debug.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
#pragma once

#include "cctweaks.h"
#include <iostream>
#include <fstream>
#include <sstream>
#include <locale>
#include <chrono>
#include <ctime>
#include <cstdlib>
#include <string>
#include <type_traits>
#include <codecvt>
#include <string_view>
#include <unistd.h>
#include <iomanip>
#include <mutex>

/** This ABORT_* / ASSERT_* have following distinctions comparing to abort/assert:
* - Errors logged into ~/.config/far2l/crash.log
Expand All @@ -21,3 +36,147 @@ void FN_NORETURN FN_PRINTF_ARGS(1) Panic(const char *format, ...) noexcept;

#define DBGLINE fprintf(stderr, "%d %d @%s\n", getpid(), __LINE__, __FILE__)

static std::mutex dumper_mutex;

inline std::string dump_escape_string(const std::string &input)
{
std::ostringstream output;

for (unsigned char c : input) {

if (c > '\x1F') {
output << c;
} else {
switch (c) {
case '\t': output << "\\t"; break;
case '\r': output << "\\r"; break;
case '\n': output << "\\n"; break;
case '\a': output << "\\a"; break;
case '\b': output << "\\b"; break;
case '\v': output << "\\v"; break;
case '\f': output << "\\f"; break;
case '\e': output << "\\e"; break;
// case '\\': output << "\\\\"; break;
default:
output << "\\x{" << std::setfill('0') << std::setw(2) << std::right<< std::hex << static_cast<unsigned int>(c) << "}";
break;
}
}
}
return output.str();
}

template <typename T>
inline void dump_value(
std::ostringstream& oss,
std::string_view var_name,
const T& value)
{

if constexpr (std::is_convertible_v<T, const wchar_t*>) {
std::wstring_convert<std::codecvt_utf8<wchar_t>> conv;
dump_value(oss, var_name, conv.to_bytes(value));
return;
}

if constexpr (std::is_convertible_v<T, std::string_view> || std::is_same_v<T, char> || std::is_same_v<T, wchar_t>) {
std::string s_value{ value };
std::string escaped = dump_escape_string(s_value);
oss << "|=> " << var_name << " = " << escaped << std::endl;
} else {
oss << "|=> " << var_name << " = " << value << std::endl;
}
}

template <>
inline void dump_value(
std::ostringstream& oss,
std::string_view var_name,
const std::wstring& value)
{
std::wstring_convert<std::codecvt_utf8<wchar_t>> conv;
dump_value(oss, var_name, conv.to_bytes(value));
}

template <typename T>
struct DumpBuffer {
DumpBuffer(T* data, size_t length)
: data(data), length(length) {}
T* data;
size_t length;
};

template <typename T>
inline void dump_value(
std::ostringstream& oss,
std::string_view var_name,
const DumpBuffer<T>& buffer) {

if constexpr (std::is_same_v<std::remove_cv_t<T>, char> || std::is_same_v<std::remove_cv_t<T>, unsigned char>) {
std::string s_value ((char*)buffer.data, buffer.length);
dump_value(oss, var_name, s_value);
} else if constexpr (std::is_same_v<std::remove_cv_t<T>, wchar_t>) {
std::wstring ws_value (buffer.data, buffer.length);
dump_value(oss, var_name, ws_value);
} else {
oss << "|=> " << var_name << " : ERROR, UNSUPPORTED TYPE!" << std::endl;
}
}

template<typename T, typename... Args>
void dump(
std::ostringstream& oss,
bool to_file,
bool firstcall,
std::string_view func_name,
std::string_view location,
pid_t pID,
unsigned int tID,
std::string_view var_name,
const T& value,
const Args&... args)
{
if (firstcall) {
auto now = std::chrono::system_clock::now();
auto time_t_now = std::chrono::system_clock::to_time_t(now);
std::tm tm_now{};
localtime_r(&time_t_now, &tm_now);
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()) % 1000;

char buffer[80];
strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &tm_now);
std::string time_str = std::string(buffer);
oss << std::endl << "/-----[PID:" << pID << ", TID:" << tID << "]-----[" << time_str << ","<< ms.count() << "]-----" << std::endl;
oss << "|[" << location << "] in " << func_name << "()" << std::endl;
}

dump_value(oss, var_name, value);

if constexpr (sizeof...(args) > 0) {
dump(oss, to_file, false, func_name, location, pID, tID, args...);
} else {
std::string log_entry = oss.str();

std::lock_guard<std::mutex> lock(dumper_mutex);

if (to_file) {
std::ofstream(std::string(std::getenv("HOME")) + "/far2l_debug.log", std::ios::app) << log_entry << std::endl;
} else {
std::clog << log_entry << std::endl;
}
}
}

#define STRINGIZE(x) #x
#define STRINGIZE_VALUE_OF(x) STRINGIZE(x)
#define LOCATION (__FILE__ ":" STRINGIZE_VALUE_OF(__LINE__))

#ifdef _FAR2L_PROJECT
#define DUMP(to_file, ...) { std::ostringstream oss; dump(oss, to_file, true, __func__, LOCATION, getpid(), GetInterThreadID(), __VA_ARGS__); }
#else
#define DUMP(to_file, ...) { std::ostringstream oss; dump(oss, to_file, true, __func__, LOCATION, getpid(), 0, __VA_ARGS__); }
#endif

#define DVV(xxx) #xxx, xxx
#define DMSG(xxx) "msg", xxx
#define DBUF(ptr,length) #ptr, DumpBuffer(ptr,length)