diff --git a/' b/' new file mode 100644 index 0000000..cac9b8d --- /dev/null +++ b/' @@ -0,0 +1,131 @@ +#include "sled/strings/utils.h" +#include +#include + +namespace sled { + +char +ToLower(char c) +{ + return ::tolower(c); +} + +char +ToUpper(char c) +{ + return ::toupper(c); +} + +std::string +ToLower(const std::string &str) +{ + std::stringstream ss; + for (auto &ch : str) { ss << ToLower(ch); } + return ss.str(); +} + +std::string +ToUpper(const std::string &str) +{ + std::stringstream ss; + for (auto &ch : str) { ss << ToUpper(ch); } + return ss.str(); +} + +std::string +StrJoin(const std::vector &strings, const std::string &delim, bool skip_empty) +{ + if (strings.empty()) { return ""; } + + std::stringstream ss; + size_t i = 0; + while (skip_empty && i < strings.size() && strings[i].empty()) { ++i; } + if (i < strings.size()) { ss << strings[i++]; } + for (; i < strings.size(); ++i) { + if (skip_empty && strings[i].empty()) { continue; } + ss << delim << strings[i]; + } + return ss.str(); +} + +std::vector +StrSplit(const std::string &str, const std::string &delim, bool skip_empty) +{ + std::vector result; + if (str.empty()) { return result; } + + size_t start = 0; + size_t next_pos = str.find_first_of(delim, start); + while (next_pos != std::string::npos) { + if ((!skip_empty && next_pos == start) || next_pos > start) { + result.emplace_back(str.begin() + start, str.begin() + next_pos); + } + + if (!skip_empty) { + start = next_pos + 1; + next_pos = str.find_first_of(delim, start); + } else { + start = str.find_first_not_of(delim, next_pos); + if (start == std::string::npos) { + // all remaining characters are delimiters + break; + } + next_pos = str.find_first_of(delim, start); + } + } + + if (start < str.size()) { result.emplace_back(str.substr(start)); } + return result; +} + +std::string +Trim(const std::string &str, const std::string &chars) +{ + return TrimLeft(TrimRight(str, chars), chars); +} + +std::string +TrimLeft(const std::string &str, const std::string &chars) +{ + size_t start = str.find_first_not_of(chars); + return start == std::string::npos ? "" : str.substr(start); +} + +std::string +TrimRight(const std::string &str, const std::string &chars) +{ + size_t end = str.find_last_not_of(chars); + return end == std::string::npos ? "" : str.substr(0, end + 1); +} + +bool +EndsWith(const std::string &str, const std::string &suffix) +{ + return str.size() >= suffix.size() && str.compare(str.size() - suffix.size(), suffix.size(), suffix) == 0; +} + +bool +StartsWith(const std::string &str, const std::string &prefix) +{ + return str.size() >= prefix.size() && str.compare(0, prefix.size(), prefix) == 0; +} + +bool +EndsWithIgnoreCase(const std::string &str, const std::string &suffix) +{ + return EndsWith(ToLower(str), ToLower(suffix)); +} + +bool +StartsWithIgnoreCase(const std::string &str, const std::string &prefix) +{ + return StartsWith(ToLower(str), ToLower(prefix)); +} + +bool +EqualsIgnoreCase(const std::string &lhs, const std::string &rhs) +{ + return ToLower(lhs) == ToLower(rhs); +} + +}// namespace sled diff --git a/CMakeLists.txt b/CMakeLists.txt index 28566a2..3f08e74 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,6 +45,7 @@ target_include_directories(sled PUBLIC src/ 3party/eigen 3party/inja target_sources( sled PRIVATE src/sled/async/async.cc + src/sled/debugging/symbolize.cc src/sled/filesystem/path.cc src/sled/log/log.cc src/sled/network/async_resolver.cc @@ -112,6 +113,32 @@ if(SLED_BUILD_BENCHMARK) benchmark::benchmark_main) endif(SLED_BUILD_BENCHMARK) +function(sled_add_test) + set(prefix SLED_TEST) + # set(options INC_DIRS LIBS) + set(one_value_keywords NAME) + set(multi_value_keywords SRCS INC_DIRS LIBS) + cmake_parse_arguments("${prefix}" "${options}" "${one_value_keywords}" + "${multi_value_keywords}" ${ARGN}) + + message(WARNING "SLED_TEST_NAME: ${SLED_TEST_NAME}") + message(WARNING "SLED_TEST_SRCS: ${SLED_TEST_SRCS}") + message(WARNING "SLED_TEST_LIBS: ${SLED_TEST_LIBS}") + add_executable(${SLED_TEST_NAME} ${SLED_TEST_SRCS}) + + if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + set(EXTRA_FLAGS -Wthread-safety -g -fsanitize=address + -fno-omit-frame-pointer -fno-optimize-sibling-calls) + target_compile_options(${SLED_TEST_NAME} PRIVATE ${EXTRA_FLAGS}) + + target_link_options(${SLED_TEST_NAME} PRIVATE ${EXTRA_FLAGS}) + endif() + target_include_directories(${SLED_TEST_NAME} PRIVATE ${SLED_TEST_INC_DIRS}) + target_link_libraries(${SLED_TEST_NAME} PRIVATE ${SLED_TEST_LIBS} + GTest::gtest) + add_test(NAME ${SLED_TEST_NAME} COMMAND ${SLED_TEST_NAME}) +endfunction() + if(SLED_BUILD_TESTS) include(FetchContent) FetchContent_Declare( @@ -119,17 +146,16 @@ if(SLED_BUILD_TESTS) URL https://github.com/google/googletest/archive/03597a01ee50ed33e9dfd640b249b4be3799d395.zip ) FetchContent_MakeAvailable(googletest) - add_executable( - sled_tests - # src/sled/exec/just_test.cc + + sled_add_test( + NAME + sled_base_test + SRCS src/sled/async/async_test.cc src/sled/any_test.cc src/sled/filesystem/path_test.cc - # src/sled/futures/promise_test.cc src/sled/futures/detail/just_test.cc src/sled/log/fmt_test.cc - # src/sled/profiling/profiling_test.cc - src/sled/strings/base64_test.cc src/sled/synchronization/sequence_checker_test.cc src/sled/cleanup_test.cc src/sled/status_test.cc @@ -137,16 +163,23 @@ if(SLED_BUILD_TESTS) src/sled/system/fiber/fiber_test.cc src/sled/system/thread_pool_test.cc src/sled/rx_test.cc - src/sled/uri_test.cc) - if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") - set(EXTRA_FLAGS -Wthread-safety -g -fsanitize=address - -fno-omit-frame-pointer -fno-optimize-sibling-calls) - target_compile_options(sled_tests PRIVATE ${EXTRA_FLAGS}) + src/sled/uri_test.cc + LIBS + sled + GTest::gtest_main) - target_link_options(sled_tests PRIVATE ${EXTRA_FLAGS}) - endif() - target_link_libraries(sled_tests PRIVATE sled GTest::gtest GTest::gtest_main) - add_test(NAME sled_tests COMMAND sled_tests) + sled_add_test( + NAME + sled_strings_test + SRCS + src/sled/strings/utils_test.cc + src/sled/strings/base64_test.cc + LIBS + sled + GTest::gtest_main) + + sled_add_test(NAME sled_symbolize_test SRCS + src/sled/debugging/symbolize_test.cc LIBS sled) endif(SLED_BUILD_TESTS) if(SLED_BUILD_FUZZ) diff --git a/src/sled/debugging/symbolize.cc b/src/sled/debugging/symbolize.cc new file mode 100644 index 0000000..b3d024c --- /dev/null +++ b/src/sled/debugging/symbolize.cc @@ -0,0 +1,25 @@ +#include "sled/debugging/symbolize.h" + +#ifdef _WIN32 +void +InitializeSymbolizer(const char *argv0) +{} + +bool +Symbolize(const void *pc, char *out, int out_size) +{ + return false; +} +#elif defined(__APPLE__) +void +InitializeSymbolizer(const char *argv0) +{} + +bool +Symbolize(const void *pc, char *out, int out_size) +{ + return false; +} +#elif defined(__linux__) +#include "sled/debugging/symbolize_elf.inc" +#endif diff --git a/src/sled/debugging/symbolize.h b/src/sled/debugging/symbolize.h new file mode 100644 index 0000000..9f3b89a --- /dev/null +++ b/src/sled/debugging/symbolize.h @@ -0,0 +1,10 @@ +#ifndef SLED_DEBUGGING_SYMBOLIZE_H +#define SLED_DEBUGGING_SYMBOLIZE_H +#pragma once + +namespace sled { +void InitializeSymbolizer(const char *argv0); +bool Symbolize(const void *pc, char *out, int out_size); +bool ReadAddrMap(); +}// namespace sled +#endif// SLED_DEBUGGING_SYMBOLIZE_H diff --git a/src/sled/debugging/symbolize_elf.inc b/src/sled/debugging/symbolize_elf.inc new file mode 100644 index 0000000..ab51283 --- /dev/null +++ b/src/sled/debugging/symbolize_elf.inc @@ -0,0 +1,140 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace sled { + +static constexpr char kTag[] = "symbolizer"; +static char *g_exe = nullptr; + +static const uint64_t +GetHex(const char *start, const char *end) +{ + uint64_t hex = 0; + for (const char *p = start; p < end; ++p) { + int ch = *p; + if (ch >= '0' && ch <= '9') { + hex = hex * 16 + (ch - '0'); + } else if (ch >= 'a' && ch <= 'f') { + hex = hex * 16 + (ch - 'a' + 10); + } else if (ch >= 'A' && ch <= 'F') { + hex = hex * 16 + (ch - 'A' + 10); + } else { + break; + } + } + + return hex; +} + +static const uint64_t +GetHex(const std::string &str) +{ + return GetHex(str.c_str(), str.c_str() + str.size()); +} + +struct ObjFile { + ObjFile() : filename(nullptr), start_addr(nullptr), end_addr(nullptr), offset(0), fd(-1), elf_type(-1) + { + memset(&elf_header, 0, sizeof(elf_header)); + memset(phdrs.data(), 0, sizeof(ElfW(Phdr)) * phdrs.size()); + } + + char *filename; + const void *start_addr; + const void *end_addr; + uint64_t offset; + + int fd; + int elf_type; + ElfW(Ehdr) elf_header; + + std::array phdrs; +}; + +std::string +ReadFullFile(int fd) +{ + std::stringstream ss; + char buf[4096]; + ssize_t n; + while ((n = read(fd, buf, sizeof(buf))) > 0) { ss.write(buf, n); } + return ss.str(); +} + +bool +ReadAddrMap() +{ + char maps_path[80]; + snprintf(maps_path, sizeof(maps_path), "/proc/self/task/%d/maps", getpid()); + int maps_fd; + do { + maps_fd = open(maps_path, O_RDONLY); + } while (maps_fd < 0 && errno == EINTR); + + if (maps_fd < 0) { + LOGE(kTag, "open {} failed, {}", maps_path, strerror(errno)); + return false; + } + + std::string full_file = ReadFullFile(maps_fd); + auto lines = sled::StrSplit(full_file, "\n"); + + // maps start_addr-end_addr permission offset dev inode pathname + for (const auto &line : lines) { + auto fields = sled::StrSplit(line, " \n\t", true); + if (fields.size() < 6) { continue; } + auto addrs = sled::StrSplit(fields[0], "-", false); + + uint64_t start_addr = GetHex(addrs[0]); + uint64_t end_addr = GetHex(addrs[1]); + uint64_t offset = GetHex(fields[2]); + LOGD(kTag, "addr: {}-{} {} {} {}", start_addr, end_addr, offset, fields[1], fields[5]); + } + + return true; +} + +class Symbolizer {}; + +void +InitializeSymbolizer(const char *argv0) +{ + if (g_exe) { + free(g_exe); + g_exe = nullptr; + } + + if (argv0 && argv0[0] != '\0') { g_exe = strdup(argv0); } +} + +bool +Symbolize(const void *pc, char *out, int out_size) +{ + Dl_info info; + if (!dladdr(pc, &info)) { return false; } + if (info.dli_fname && out_size > 0) { + strncpy(out, info.dli_fname, out_size); + + const auto end_pos = static_cast(out_size) - 1; + if (out[end_pos] != '\0') { + // add ... + out[end_pos] = '\0'; + } + } + + return true; +} +}// namespace sled diff --git a/src/sled/debugging/symbolize_test.cc b/src/sled/debugging/symbolize_test.cc new file mode 100644 index 0000000..8a5272a --- /dev/null +++ b/src/sled/debugging/symbolize_test.cc @@ -0,0 +1,43 @@ +#include +#include +#include + +void +TestFunc1() +{} + +static char try_symbolize_buffer[4096]; + +static const char * +TrySymbolizeWithLimit(void *pc, int limit) +{ + // ASSERT_LE(limit, 4096); + auto heap_buffer = sled::MakeUnique(4096); + bool found = sled::Symbolize(pc, heap_buffer.get(), sizeof(try_symbolize_buffer)); + strncpy(try_symbolize_buffer, heap_buffer.get(), sizeof(try_symbolize_buffer) - 1); + try_symbolize_buffer[sizeof(try_symbolize_buffer) - 1] = '\0'; + + return try_symbolize_buffer; +} + +static const char * +TrySymbolize(void *pc) +{ + return TrySymbolizeWithLimit(pc, 4096); +} + +// TEST(Symbolize, base) +// { +// char buf[1024]; +// EXPECT_EQ("TestFunc1", TrySymbolize((void *) &TestFunc1)); +// } + +TEST(Symbolize, ReadAddrMap) { sled::ReadAddrMap(); } + +int +main(int argc, char *argv[]) +{ + sled::InitializeSymbolizer(argv[0]); + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/src/sled/strings/utils.cc b/src/sled/strings/utils.cc index c41658b..dd40eca 100644 --- a/src/sled/strings/utils.cc +++ b/src/sled/strings/utils.cc @@ -54,15 +54,31 @@ StrSplit(const std::string &str, const std::string &delim, bool skip_empty) std::vector result; if (str.empty()) { return result; } - size_t start = 0; + size_t start = 0; size_t next_pos = str.find_first_of(delim, start); while (next_pos != std::string::npos) { - if (!skip_empty || next_pos > start) { result.emplace_back(str.substr(start, next_pos - start)); } - start = next_pos + 1; - next_pos = str.find_first_of(delim, start); + if ((!skip_empty && next_pos == start) || next_pos > start) { + result.emplace_back(str.begin() + start, str.begin() + next_pos); + } + + if (!skip_empty) { + start = next_pos + 1; + next_pos = str.find_first_of(delim, start); + } else { + start = str.find_first_not_of(delim, next_pos); + if (start == std::string::npos) { + // all remaining characters are delimiters + break; + } + next_pos = str.find_first_of(delim, start); + } } - if (start < str.size()) { result.emplace_back(str.substr(start)); } + if (start < str.size()) { + result.emplace_back(str.substr(start)); + } else if (!skip_empty && !str.empty() && delim.find(str.back()) != std::string::npos) { + result.emplace_back(""); + } return result; } diff --git a/src/sled/strings/utils_test.cc b/src/sled/strings/utils_test.cc new file mode 100644 index 0000000..7d2f7ce --- /dev/null +++ b/src/sled/strings/utils_test.cc @@ -0,0 +1,99 @@ +#include +#include + +TEST(ToLower, Char) +{ + EXPECT_EQ(sled::ToLower('A'), 'a'); + EXPECT_EQ(sled::ToLower('Z'), 'z'); + EXPECT_EQ(sled::ToLower('a'), 'a'); + EXPECT_EQ(sled::ToLower('z'), 'z'); + EXPECT_EQ(sled::ToLower(' '), ' '); + EXPECT_EQ(sled::ToLower('\0'), '\0'); +} + +TEST(ToUpper, Char) +{ + EXPECT_EQ(sled::ToUpper('A'), 'A'); + EXPECT_EQ(sled::ToUpper('Z'), 'Z'); + EXPECT_EQ(sled::ToUpper('a'), 'A'); + EXPECT_EQ(sled::ToUpper('z'), 'Z'); + EXPECT_EQ(sled::ToUpper(' '), ' '); + EXPECT_EQ(sled::ToUpper('\0'), '\0'); +} + +TEST(ToLower, String) +{ + EXPECT_EQ(sled::ToLower("Hello World"), "hello world"); + EXPECT_EQ(sled::ToLower("HELLO WORLD"), "hello world"); + EXPECT_EQ(sled::ToLower("hello world"), "hello world"); + EXPECT_EQ(sled::ToLower(" "), " "); + EXPECT_EQ(sled::ToLower(""), ""); +} + +TEST(ToUpper, String) +{ + EXPECT_EQ(sled::ToUpper("Hello World"), "HELLO WORLD"); + EXPECT_EQ(sled::ToUpper("HELLO WORLD"), "HELLO WORLD"); + EXPECT_EQ(sled::ToUpper("hello world"), "HELLO WORLD"); + EXPECT_EQ(sled::ToUpper(" "), " "); + EXPECT_EQ(sled::ToUpper(""), ""); +} + +TEST(StrJoin, Empty) +{ + EXPECT_EQ(sled::StrJoin({}, ","), ""); + EXPECT_EQ(sled::StrJoin({}, ",", true), ""); + EXPECT_EQ(sled::StrJoin({}, ",", false), ""); +} + +TEST(StrJoin, Delim) +{ + EXPECT_EQ(sled::StrJoin({"a", "b", "c"}, ","), "a,b,c"); + EXPECT_EQ(sled::StrJoin({"a", "b", "c"}, ",", true), "a,b,c"); + EXPECT_EQ(sled::StrJoin({"a", "b", "c"}, ",", false), "a,b,c"); +} + +TEST(StrJoin, DelimSkipEmpty) +{ + EXPECT_EQ(sled::StrJoin({"a", "", "c"}, ","), "a,,c"); + EXPECT_EQ(sled::StrJoin({"a", "", "c"}, ",", true), "a,c"); + EXPECT_EQ(sled::StrJoin({"a", "", "c"}, ",", false), "a,,c"); +} + +TEST(StrSplit, Empty) +{ + EXPECT_EQ(sled::StrSplit("", ","), std::vector()); + EXPECT_EQ(sled::StrSplit("", ",", true), std::vector()); + EXPECT_EQ(sled::StrSplit("", ",", false), std::vector()); +} + +TEST(StrSplit, Delim) +{ + // single delim + EXPECT_EQ(sled::StrSplit("a,b,c", ","), std::vector({"a", "b", "c"})); + EXPECT_EQ(sled::StrSplit("a,b,c", ",", true), std::vector({"a", "b", "c"})); + EXPECT_EQ(sled::StrSplit("a,b,c", ",", false), std::vector({"a", "b", "c"})); + EXPECT_EQ(sled::StrSplit("a,b,c,", ","), std::vector({"a", "b", "c", ""})); + EXPECT_EQ(sled::StrSplit("a,b,c,", ",", true), std::vector({"a", "b", "c"})); + EXPECT_EQ(sled::StrSplit("a,b,c,", ",", false), std::vector({"a", "b", "c", ""})); + EXPECT_EQ(sled::StrSplit(",a,b,c", ","), std::vector({"", "a", "b", "c"})); + EXPECT_EQ(sled::StrSplit(",a,b,c", ",", true), std::vector({"a", "b", "c"})); + EXPECT_EQ(sled::StrSplit(",a,b,c", ",", false), std::vector({"", "a", "b", "c"})); + + // multi delim + EXPECT_EQ(sled::StrSplit(",,a,b,c", ",", true), std::vector({"a", "b", "c"})); + EXPECT_EQ(sled::StrSplit(",,a,b,c", ",", false), std::vector({"", "", "a", "b", "c"})); + EXPECT_EQ(sled::StrSplit("a,b,c,,", ",", true), std::vector({"a", "b", "c"})); + EXPECT_EQ(sled::StrSplit("a,b,c,,", ",", false), std::vector({"a", "b", "c", "", ""})); + EXPECT_EQ(sled::StrSplit("a,,b,c", ",", true), std::vector({"a", "b", "c"})); + EXPECT_EQ(sled::StrSplit("a,,b,c", ",", false), std::vector({"a", "", "b", "c"})); +} + +TEST(StrSplit, MultiDelim) +{ + EXPECT_EQ(sled::StrSplit("a,b;c", ",;", true), std::vector({"a", "b", "c"})); + EXPECT_EQ(sled::StrSplit("a,b;c", ",;", false), std::vector({"a", "b", "c"})); + EXPECT_EQ(sled::StrSplit("a,b;c,", ",;", true), std::vector({"a", "b", "c"})); + EXPECT_EQ(sled::StrSplit("a,b;c,", ",;", false), std::vector({"a", "b", "c", ""})); + EXPECT_EQ(sled::StrSplit("a,b;c,", ";,", true), std::vector({"a", "b", "c"})); +}