Skip to content

Commit eb048a2

Browse files
chore(profiling): backport memcpy
1 parent 08c227e commit eb048a2

File tree

4 files changed

+359
-9
lines changed

4 files changed

+359
-9
lines changed
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
#include <echion/danger.h>
2+
#include <echion/state.h>
3+
4+
#include <signal.h>
5+
#include <string.h>
6+
#include <sys/mman.h>
7+
#include <unistd.h>
8+
#include <algorithm>
9+
#include <cassert>
10+
#include <cerrno>
11+
#include <csetjmp>
12+
#include <cstdio>
13+
14+
static const size_t page_size = []() -> size_t {
15+
auto v = sysconf(_SC_PAGESIZE);
16+
17+
#ifdef PL_DARWIN
18+
if (v <= 0)
19+
{
20+
// Fallback on macOS just in case
21+
v = getpagesize();
22+
}
23+
#endif
24+
25+
if (v <= 0)
26+
{
27+
fprintf(stderr, "Failed to detect page size, falling back to 4096\n");
28+
return 4096;
29+
}
30+
31+
return v;
32+
}();
33+
34+
struct sigaction g_old_segv;
35+
struct sigaction g_old_bus;
36+
37+
thread_local ThreadAltStack t_altstack;
38+
39+
// We "arm" by publishing a valid jmp env for this thread.
40+
thread_local sigjmp_buf t_jmpenv;
41+
thread_local volatile sig_atomic_t t_handler_armed = 0;
42+
43+
static inline void arm_fault_handler()
44+
{
45+
t_handler_armed = 1;
46+
__asm__ __volatile__("" ::: "memory");
47+
}
48+
49+
static inline void disarm_fault_handler()
50+
{
51+
__asm__ __volatile__("" ::: "memory");
52+
t_handler_armed = 0;
53+
}
54+
55+
static void segv_handler(int signo, siginfo_t*, void*)
56+
{
57+
if (!t_handler_armed)
58+
{
59+
struct sigaction* old = (signo == SIGSEGV) ? &g_old_segv : &g_old_bus;
60+
// Restore the previous handler and re-raise so default/old handling occurs.
61+
sigaction(signo, old, nullptr);
62+
raise(signo);
63+
return;
64+
}
65+
66+
// Jump back to the armed site. Use 1 so sigsetjmp returns nonzero.
67+
siglongjmp(t_jmpenv, 1);
68+
}
69+
70+
int init_segv_catcher()
71+
{
72+
if (t_altstack.ensure_installed() != 0)
73+
{
74+
return -1;
75+
}
76+
77+
struct sigaction sa
78+
{
79+
};
80+
sa.sa_sigaction = segv_handler;
81+
sigemptyset(&sa.sa_mask);
82+
83+
// SA_SIGINFO for 3-arg handler; SA_ONSTACK to run on alt stack; SA_NODEFER to avoid having to
84+
// use savemask
85+
sa.sa_flags = SA_SIGINFO | SA_ONSTACK | SA_NODEFER;
86+
if (sigaction(SIGSEGV, &sa, &g_old_segv) != 0)
87+
{
88+
return -1;
89+
}
90+
if (sigaction(SIGBUS, &sa, &g_old_bus) != 0)
91+
{
92+
// Try to roll back SIGSEGV install on failure.
93+
sigaction(SIGSEGV, &g_old_segv, nullptr);
94+
return -1;
95+
}
96+
97+
return 0;
98+
}
99+
100+
#if defined PL_LINUX
101+
using safe_memcpy_return_t = ssize_t;
102+
#elif defined PL_DARWIN
103+
using safe_memcpy_return_t = mach_vm_size_t;
104+
#endif
105+
106+
safe_memcpy_return_t safe_memcpy(void* dst, const void* src, size_t n)
107+
{
108+
if (t_altstack.ensure_installed() != 0)
109+
{
110+
errno = EINVAL;
111+
return -1;
112+
}
113+
114+
bool t_faulted = false;
115+
116+
auto* d = static_cast<uint8_t*>(dst);
117+
auto* s = static_cast<const uint8_t*>(src);
118+
safe_memcpy_return_t rem = static_cast<safe_memcpy_return_t>(n);
119+
120+
arm_fault_handler();
121+
if (sigsetjmp(t_jmpenv, /* save sig mask = */ 0) != 0)
122+
{
123+
// We arrived here from siglongjmp after a fault.
124+
t_faulted = true;
125+
goto landing;
126+
}
127+
128+
// Copy in page-bounded chunks (at most one fault per bad page).
129+
while (rem)
130+
{
131+
safe_memcpy_return_t to_src_pg =
132+
page_size - (static_cast<uintptr_t>(reinterpret_cast<uintptr_t>(s)) & (page_size - 1));
133+
safe_memcpy_return_t to_dst_pg =
134+
page_size - (static_cast<uintptr_t>(reinterpret_cast<uintptr_t>(d)) & (page_size - 1));
135+
safe_memcpy_return_t chunk = std::min(rem, std::min(to_src_pg, to_dst_pg));
136+
137+
// Optional early probe to fault before entering large memcpy
138+
(void)*reinterpret_cast<volatile const uint8_t*>(s);
139+
140+
// If this faults, we'll siglongjmp back to the sigsetjmp above.
141+
(void)memcpy(d, s, static_cast<size_t>(chunk));
142+
143+
d += chunk;
144+
s += chunk;
145+
rem -= chunk;
146+
}
147+
148+
landing:
149+
disarm_fault_handler();
150+
151+
if (t_faulted)
152+
{
153+
errno = EFAULT;
154+
return -1;
155+
}
156+
157+
return static_cast<safe_memcpy_return_t>(n);
158+
}
159+
160+
#if defined PL_LINUX
161+
ssize_t safe_memcpy_wrapper(pid_t, const struct iovec* __dstvec, unsigned long int __dstiovcnt,
162+
const struct iovec* __srcvec, unsigned long int __srciovcnt,
163+
unsigned long int)
164+
{
165+
(void)__dstiovcnt;
166+
(void)__srciovcnt;
167+
assert(__dstiovcnt == 1);
168+
assert(__srciovcnt == 1);
169+
170+
size_t to_copy = std::min(__dstvec->iov_len, __srcvec->iov_len);
171+
return safe_memcpy(__dstvec->iov_base, __srcvec->iov_base, to_copy);
172+
}
173+
#elif defined PL_DARWIN
174+
kern_return_t safe_memcpy_wrapper(vm_map_read_t target_task, mach_vm_address_t address,
175+
mach_vm_size_t size, mach_vm_address_t data,
176+
mach_vm_size_t* outsize)
177+
{
178+
(void)target_task;
179+
180+
auto copied = safe_memcpy(reinterpret_cast<void*>(data), reinterpret_cast<void*>(address),
181+
static_cast<size_t>(size));
182+
*outsize = copied;
183+
return copied == size ? KERN_SUCCESS : KERN_FAILURE;
184+
}
185+
#endif
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
#include <sys/mman.h>
2+
#include <sys/syscall.h>
3+
#include <sys/uio.h>
4+
#include <unistd.h>
5+
#include <cassert>
6+
#include <csignal>
7+
#include <cstddef>
8+
#include <cstdio>
9+
#include <cstring>
10+
#include <iostream>
11+
12+
#if defined PL_DARWIN
13+
#include <pthread.h>
14+
15+
#include <mach/mach.h>
16+
#include <mach/mach_vm.h>
17+
#include <mach/machine/kern_return.h>
18+
#include <sys/sysctl.h>
19+
#include <sys/types.h>
20+
#endif
21+
22+
int init_segv_catcher();
23+
24+
#if defined PL_LINUX
25+
ssize_t safe_memcpy_wrapper(pid_t, const struct iovec* __dstvec, unsigned long int __dstiovcnt,
26+
const struct iovec* __srcvec, unsigned long int __srciovcnt,
27+
unsigned long int);
28+
#elif defined PL_DARWIN
29+
kern_return_t safe_memcpy_wrapper(vm_map_read_t target_task, mach_vm_address_t address,
30+
mach_vm_size_t size, mach_vm_address_t data,
31+
mach_vm_size_t* outsize);
32+
#endif
33+
34+
struct ThreadAltStack
35+
{
36+
private:
37+
inline static constexpr size_t kAltStackSize = 1 << 20; // 1 MiB
38+
39+
public:
40+
void* mem = nullptr;
41+
size_t size = 0;
42+
bool ready = false;
43+
44+
int ensure_installed()
45+
{
46+
if (ready)
47+
{
48+
return 0;
49+
}
50+
51+
// If an altstack is already present, keep it.
52+
stack_t cur{};
53+
if (sigaltstack(nullptr, &cur) == 0 && !(cur.ss_flags & SS_DISABLE))
54+
{
55+
ready = true;
56+
return 0;
57+
}
58+
59+
void* stack_mem = mmap(nullptr, kAltStackSize, PROT_READ | PROT_WRITE,
60+
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
61+
if (stack_mem == MAP_FAILED)
62+
{
63+
std::cerr << "Failed to allocate alt stack. Memory copying may not work."
64+
<< " Error: " << strerror(errno) << std::endl;
65+
return -1;
66+
}
67+
68+
stack_t ss{};
69+
ss.ss_sp = stack_mem;
70+
ss.ss_size = kAltStackSize;
71+
ss.ss_flags = 0;
72+
if (sigaltstack(&ss, nullptr) != 0)
73+
{
74+
munmap(stack_mem, kAltStackSize);
75+
std::cerr << "Failed to set alt stack. Memory copying may not work."
76+
<< " Error: " << strerror(errno) << std::endl;
77+
return -1;
78+
}
79+
80+
this->mem = stack_mem;
81+
this->size = kAltStackSize;
82+
this->ready = true;
83+
84+
return 0;
85+
}
86+
87+
~ThreadAltStack()
88+
{
89+
if (!ready)
90+
{
91+
return;
92+
}
93+
94+
// Optional cleanup: disable and free. Safe at thread exit.
95+
stack_t disable{};
96+
disable.ss_flags = SS_DISABLE;
97+
(void)sigaltstack(&disable, nullptr);
98+
munmap(mem, size);
99+
}
100+
};

ddtrace/internal/datadog/profiling/echion/echion/state.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@
66

77
#define PY_SSIZE_T_CLEAN
88
#include <Python.h>
9+
10+
#if PY_VERSION_HEX >= 0x030c0000
11+
// https://github.com/python/cpython/issues/108216#issuecomment-1696565797
12+
#undef _PyGC_FINALIZED
13+
#endif
14+
915
#if defined __GNUC__ && defined HAVE_STD_ATOMIC
1016
#undef HAVE_STD_ATOMIC
1117
#endif

0 commit comments

Comments
 (0)