Skip to content

Commit 3cc8ba5

Browse files
authored
Feature: support using gdb to debug fiber (#64)
1 parent 41485d6 commit 3cc8ba5

15 files changed

Lines changed: 778 additions & 2 deletions

docs/en/fiber_user_guide.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,10 @@ trpc::StartFiberDetached([&] {
342342

343343
```
344344
345+
# Debugging fibers using gdb
346+
347+
See [gdb_plugin](../../trpc/tools/gdb_plugin/)
348+
345349
# FAQ
346350
347351
See more [Fiber FAQ](./fiber_faq.md)

docs/zh/fiber_user_guide.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,10 @@ trpc::StartFiberDetached([&] {
342342

343343
```
344344
345+
# 使用gdb调试fiber
346+
347+
查阅[gdb_plugin](../../trpc/tools/gdb_plugin/)
348+
345349
# FAQ
346350
347351
查阅[Fiber FAQ](./faq/fiber_problem.md)

trpc/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,3 +108,4 @@ config_setting(
108108
name = "trpc_enable_http_transinfo_base64",
109109
values = {"define": "trpc_enable_http_transinfo_base64=true"},
110110
)
111+

trpc/common/config/global_conf.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ void FiberThreadModelInstanceConfig::Display() const {
6565
TRPC_LOG_DEBUG("fiber_pool_num_by_mmap:" << fiber_pool_num_by_mmap);
6666
TRPC_LOG_DEBUG("fiber_stack_enable_guard_page:" << fiber_stack_enable_guard_page);
6767
TRPC_LOG_DEBUG("fiber_scheduling_name:" << fiber_scheduling_name);
68+
TRPC_LOG_DEBUG("enable_gdb_debug:" << enable_gdb_debug);
6869

6970
TRPC_LOG_DEBUG("================================");
7071
}

trpc/common/config/global_conf.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,9 @@ struct FiberThreadModelInstanceConfig {
8484
/// @brief Stack overflow protect
8585
bool fiber_stack_enable_guard_page{true};
8686

87+
/// @brief Enable debug fiber using gdb
88+
bool enable_gdb_debug = false;
89+
8790
void Display() const;
8891
};
8992

trpc/common/config/global_conf_parser.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ struct convert<trpc::FiberThreadModelInstanceConfig> {
4343
node["fiber_pool_num_by_mmap"] = config.fiber_pool_num_by_mmap;
4444
node["fiber_stack_enable_guard_page"] = config.fiber_stack_enable_guard_page;
4545
node["fiber_scheduling_name"] = config.fiber_scheduling_name;
46+
node["enable_gdb_debug"] = config.enable_gdb_debug;
4647

4748
return node;
4849
}
@@ -112,6 +113,10 @@ struct convert<trpc::FiberThreadModelInstanceConfig> {
112113
config.fiber_scheduling_name = "v1";
113114
}
114115

116+
if (node["enable_gdb_debug"]) {
117+
config.enable_gdb_debug = node["enable_gdb_debug"].as<bool>();
118+
}
119+
115120
return true;
116121
}
117122
};

trpc/runtime/fiber_runtime.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ void StartRuntime() {
105105
options.pool_num_by_mmap = conf.fiber_pool_num_by_mmap;
106106
options.stack_enable_guard_page = conf.fiber_stack_enable_guard_page;
107107
options.disable_process_name = global_config.thread_disable_process_name;
108+
options.enable_gdb_debug = conf.enable_gdb_debug;
108109
} else {
109110
options.group_name = "fiber_instance";
110111
}

trpc/runtime/threadmodel/fiber/detail/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ cc_library(
2626
deps = [
2727
":assembly",
2828
"//trpc/util:check",
29+
"//trpc/util:deferred",
2930
"//trpc/util:likely",
3031
"//trpc/util/internal:never_destroyed",
3132
],

trpc/runtime/threadmodel/fiber/detail/stack_allocator_impl.cc

Lines changed: 148 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@
99
// please note that tRPC source code is licensed under the Apache 2.0 License,
1010
// A copy of the Apache 2.0 License is included in this file.
1111
//
12+
// tRPC is licensed under the Apache 2.0 License, and includes source codes from
13+
// the following components:
14+
// 1. flare
15+
// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved.
16+
// flare is licensed under the BSD 3-Clause License.
1217
//
1318

1419
#include "trpc/runtime/threadmodel/fiber/detail/stack_allocator_impl.h"
@@ -22,6 +27,7 @@
2227

2328
#include "trpc/runtime/threadmodel/fiber/detail/assembly.h"
2429
#include "trpc/util/check.h"
30+
#include "trpc/util/deferred.h"
2531
#include "trpc/util/internal/never_destroyed.h"
2632
#include "trpc/util/likely.h"
2733

@@ -31,13 +37,123 @@ static bool fiber_use_mmap = true;
3137
static uint32_t fiber_stack_size = 131072;
3238
static bool fiber_stack_enable_guard_page = true;
3339
static uint32_t max_fiber_num_by_mmap = 30 * 1024;
40+
static bool enable_gdb_debug = false;
41+
42+
const uint32_t kPageSize = getpagesize();
43+
44+
// The following source codes are from flare.
45+
// Copied and modified from
46+
// https://github.com/Tencent/flare/blob/master/flare/fiber/detail/stack_allocator.cc
47+
48+
// All stacks (whether system stack or user stack) are registered here. This is
49+
// necessary for our GDB plugin to find all the stacks.
50+
//
51+
// Only _actual_ stack allocation / deallocation needs to touch this. For
52+
// allocations / deallocations covered by our object pool, they're irrelevant
53+
// here.
54+
//
55+
// Registration / deregistration can be slow. But that's okay as it's already
56+
// slow enough to _actually_ creating / destroying stacks. These operations
57+
// incur heavy VMA operations.
58+
struct StackRegistry {
59+
// Listed as public as they're our "public" interfaces to GDB plugin.
60+
//
61+
// Code in this TU should use methods below instead of touching these fields.
62+
void** stacks = nullptr; // Leaked on exit. Doesn't matter.
63+
std::size_t used = 0;
64+
std::size_t capacity = 0;
65+
66+
// Register a newly-allocated stack.
67+
//
68+
// `ptr` should point to stack bottom (i.e. one byte past the stack region).
69+
// That's where our fiber control block (GDB plugin need it) resides.
70+
void RegisterStack(void* ptr) {
71+
std::scoped_lock _(lock_); // It's slow, so be it.
72+
++used;
73+
auto slot = UnsafeFindSlotOf(nullptr);
74+
if (slot) {
75+
*slot = ptr;
76+
return;
77+
}
78+
79+
UnsafeResizeRegistry();
80+
*UnsafeFindSlotOf(nullptr) = ptr; // Must succeed this time.
81+
}
82+
83+
// Deregister a going-to-be-freed stack. `ptr` points to stack bottom.
84+
void DeregisterStack(void* ptr) {
85+
std::scoped_lock _(lock_);
86+
87+
trpc::ScopedDeferred __([&] {
88+
// If `stacks` is too large we should consider shrinking it.
89+
if (capacity > 1024 && capacity / 2 > used) {
90+
UnsafeShrinkRegistry();
91+
}
92+
});
93+
94+
--used;
95+
if (auto p = UnsafeFindSlotOf(ptr)) {
96+
*p = nullptr;
97+
return;
98+
}
99+
TRPC_UNREACHABLE();
100+
}
101+
102+
private:
103+
void** UnsafeFindSlotOf(void* ptr) {
104+
for (std::size_t i = 0; i != capacity; ++i) {
105+
if (stacks[i] == ptr) {
106+
return &stacks[i];
107+
}
108+
}
109+
return nullptr;
110+
}
111+
112+
void UnsafeShrinkRegistry() {
113+
auto new_capacity = capacity / 2;
114+
TRPC_CHECK(new_capacity);
115+
auto new_stacks = new void*[new_capacity];
116+
std::size_t copied = 0;
117+
118+
memset(new_stacks, 0, new_capacity * sizeof(void*));
119+
for (std::size_t i = 0; i != capacity; ++i) {
120+
if (stacks[i]) {
121+
new_stacks[copied++] = stacks[i];
122+
}
123+
}
124+
125+
TRPC_CHECK_EQ(copied, used);
126+
TRPC_CHECK_LE(copied, new_capacity);
127+
capacity = new_capacity;
128+
delete[] std::exchange(stacks, new_stacks);
129+
}
130+
131+
void UnsafeResizeRegistry() {
132+
if (capacity == 0) { // We haven't been initialized yet.
133+
capacity = 8;
134+
stacks = new void*[capacity];
135+
memset(stacks, 0, sizeof(void*) * capacity);
136+
} else {
137+
auto new_capacity = capacity * 2;
138+
auto new_stacks = new void*[new_capacity];
139+
memset(new_stacks, 0, new_capacity * sizeof(void*));
140+
memcpy(new_stacks, stacks, capacity * sizeof(void*));
141+
capacity = new_capacity;
142+
delete[] std::exchange(stacks, new_stacks);
143+
}
144+
}
145+
146+
private:
147+
std::mutex lock_;
148+
} stack_registry; // Using global variable here. This makes looking up this
149+
// variable easy in GDB plugin.
150+
151+
// End of source codes that are from flare.
34152

35153
void SetFiberPoolCreateWay(bool use_mmap) {
36154
fiber_use_mmap = use_mmap;
37155
}
38156

39-
const uint32_t kPageSize = getpagesize();
40-
41157
void SetFiberStackSize(uint32_t stack_size) {
42158
bool ok = (stack_size >= kPageSize) && ((stack_size & (kPageSize - 1)) == 0);
43159
if (!ok) {
@@ -58,6 +174,10 @@ void SetFiberPoolNumByMmap(uint32_t fiber_pool_num_by_mmap) {
58174
max_fiber_num_by_mmap = fiber_pool_num_by_mmap;
59175
}
60176

177+
void SetEnableGdbDebug(bool flag) {
178+
enable_gdb_debug = flag;
179+
}
180+
61181
// We always align stack top to 1M boundary. This helps our GDB plugin to find
62182
// fiber stacks.
63183
constexpr auto kStackTopAlignment = 1 * 1024 * 1024;
@@ -141,10 +261,23 @@ void* AlignedMmap() {
141261
}
142262
auto stack = reinterpret_cast<char*>(p) + GetBias();
143263
InitializeFiberStackMagic(stack + fiber_stack_size - kPageSize);
264+
265+
if (enable_gdb_debug) {
266+
auto stack_bottom = stack + fiber_stack_size;
267+
// Register the stack.
268+
stack_registry.RegisterStack(stack_bottom);
269+
}
270+
144271
return reinterpret_cast<void*>(stack);
145272
}
146273

147274
void AlignedMunmap(void* ptr) {
275+
if (enable_gdb_debug) {
276+
// Remove the stack from our registry.
277+
auto stack_bottom = reinterpret_cast<char*>(ptr) + fiber_stack_size;
278+
stack_registry.DeregisterStack(stack_bottom);
279+
}
280+
148281
TRPC_PCHECK(munmap(reinterpret_cast<char*>(ptr) - GetBias(), GetAllocationsize()) == 0);
149282
}
150283

@@ -155,10 +288,23 @@ void* AlignedMalloc() {
155288
}
156289
auto stack = reinterpret_cast<char*>(p);
157290
InitializeFiberStackMagic(stack + fiber_stack_size - kPageSize);
291+
292+
if (enable_gdb_debug) {
293+
auto stack_bottom = stack + fiber_stack_size;
294+
// Register the stack.
295+
stack_registry.RegisterStack(stack_bottom);
296+
}
297+
158298
return reinterpret_cast<void*>(stack);
159299
}
160300

161301
void AlignedFree(void* aligned_mem) {
302+
if (enable_gdb_debug) {
303+
// Remove the stack from our registry.
304+
auto stack_bottom = reinterpret_cast<char*>(aligned_mem) + fiber_stack_size;
305+
stack_registry.DeregisterStack(stack_bottom);
306+
}
307+
162308
return free(aligned_mem);
163309
}
164310

trpc/runtime/threadmodel/fiber/detail/stack_allocator_impl.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ void SetFiberStackEnableGuardPage(bool flag);
3636
/// @brief Set the number of pooled memory stacks allocated through mmap used by fibers
3737
void SetFiberPoolNumByMmap(uint32_t fiber_pool_num_by_mmap);
3838

39+
/// @brief Enable debug fiber using gdb
40+
void SetEnableGdbDebug(bool flag);
41+
3942
/// @brief Pre-allocate a certain number of fiber memory stacks
4043
/// @return The number of successfully warmed up fiber memory blocks
4144
int PrewarmFiberPool(uint32_t fiber_num);

0 commit comments

Comments
 (0)