1 | // Copyright (C) 2022 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | |
4 | #include <private/qv4stacklimits_p.h> |
5 | #include <private/qobject_p.h> |
6 | #include <private/qthread_p.h> |
7 | |
8 | #include <QtCore/qfile.h> |
9 | |
10 | #if defined(Q_OS_UNIX) |
11 | # include <pthread.h> |
12 | #endif |
13 | |
14 | #ifdef Q_OS_WIN |
15 | # include <QtCore/qt_windows.h> |
16 | #elif defined(Q_OS_FREEBSD_KERNEL) || defined(Q_OS_OPENBSD) |
17 | # include <pthread_np.h> |
18 | #elif defined(Q_OS_LINUX) |
19 | # include <unistd.h> |
20 | # include <sys/resource.h> // for getrlimit() |
21 | # include <sys/syscall.h> // for SYS_gettid |
22 | # if defined(__GLIBC__) && QT_CONFIG(dlopen) |
23 | # include <dlfcn.h> |
24 | # endif |
25 | #elif defined(Q_OS_DARWIN) |
26 | # include <sys/resource.h> // for getrlimit() |
27 | #elif defined(Q_OS_QNX) |
28 | # include <devctl.h> |
29 | # include <sys/procfs.h> |
30 | # include <sys/types.h> |
31 | # include <unistd.h> |
32 | #elif defined(Q_OS_INTEGRITY) |
33 | # include <INTEGRITY.h> |
34 | #elif defined(Q_OS_WASM) |
35 | # include <emscripten/stack.h> |
36 | #endif |
37 | |
38 | QT_BEGIN_NAMESPACE |
39 | |
40 | namespace QV4 { |
41 | |
42 | enum StackDefaults : qsizetype { |
43 | // Default safety margin at the end of the usable stack. |
44 | // Since we don't check the stack on every instruction, we might overrun our soft limit. |
45 | DefaultSafetyMargin = 128 * 1024, |
46 | #if defined(Q_OS_IOS) |
47 | PlatformStackSize = 1024 * 1024, |
48 | PlatformSafetyMargin = DefaultSafetyMargin, |
49 | #elif defined(Q_OS_MACOS) |
50 | PlatformStackSize = 8 * 1024 * 1024, |
51 | PlatformSafetyMargin = DefaultSafetyMargin, |
52 | #elif defined(Q_OS_ANDROID) |
53 | // Android appears to have 1MB stacks. |
54 | PlatformStackSize = 1024 * 1024, |
55 | PlatformSafetyMargin = DefaultSafetyMargin, |
56 | #elif defined(Q_OS_LINUX) |
57 | // On linux, we assume 8MB stacks if rlimit doesn't work. |
58 | PlatformStackSize = 8 * 1024 * 1024, |
59 | PlatformSafetyMargin = DefaultSafetyMargin, |
60 | #elif defined(Q_OS_QNX) |
61 | // QNX's stack is only 512k by default |
62 | PlatformStackSize = 512 * 1024, |
63 | PlatformSafetyMargin = DefaultSafetyMargin, |
64 | #else |
65 | // We try to claim 512k if we don't know anything else. |
66 | PlatformStackSize = 512 * 1024, |
67 | PlatformSafetyMargin = DefaultSafetyMargin, |
68 | #endif |
69 | }; |
70 | |
71 | // We may not be able to take the negative of the type |
72 | // used to represent stack size, but we can always add |
73 | // or subtract it to/from a quint8 pointer. |
74 | |
75 | template<typename Size> |
76 | static void *incrementStackPointer(void *base, Size amount) |
77 | { |
78 | #if Q_STACK_GROWTH_DIRECTION > 0 |
79 | return static_cast<quint8 *>(base) + amount; |
80 | #else |
81 | return static_cast<quint8 *>(base) - amount; |
82 | #endif |
83 | } |
84 | |
85 | template<typename Size> |
86 | static void *decrementStackPointer(void *base, Size amount) |
87 | { |
88 | #if Q_STACK_GROWTH_DIRECTION > 0 |
89 | return static_cast<quint8 *>(base) - amount; |
90 | #else |
91 | return static_cast<quint8 *>(base) + amount; |
92 | #endif |
93 | } |
94 | |
95 | static StackProperties createStackProperties(void *base, qsizetype size = PlatformStackSize) |
96 | { |
97 | return StackProperties { |
98 | .base: base, |
99 | .softLimit: incrementStackPointer(base, amount: size - PlatformSafetyMargin), |
100 | .hardLimit: incrementStackPointer(base, amount: size), |
101 | }; |
102 | } |
103 | |
104 | #if defined(Q_OS_DARWIN) || defined(Q_OS_LINUX) |
105 | |
106 | // On linux and darwin, on the main thread, the pthread functions |
107 | // may not return the true stack size since the main thread stack |
108 | // may grow. Use rlimit instead. rlimit does not work for secondary |
109 | // threads, though. If getrlimit fails, we assume the platform |
110 | // stack size. |
111 | static qsizetype getMainStackSizeFromRlimit() |
112 | { |
113 | rlimit limit; |
114 | return (getrlimit(RLIMIT_STACK, rlimits: &limit) == 0 && limit.rlim_cur != RLIM_INFINITY) |
115 | ? qsizetype(limit.rlim_cur) |
116 | : qsizetype(PlatformStackSize); |
117 | } |
118 | #endif |
119 | |
120 | #if defined(Q_OS_INTEGRITY) |
121 | |
122 | StackProperties stackProperties() |
123 | { |
124 | Address stackLow, stackHigh; |
125 | CheckSuccess(GetTaskStackLimits(CurrentTask(), &stackLow, &stackHigh)); |
126 | # if Q_STACK_GROWTH_DIRECTION < 0 |
127 | return createStackProperties(reinterpret_cast<void *>(stackHigh), stackHigh - stackLow); |
128 | # else |
129 | return createStackProperties(reinterpret_cast<void *>(stackLow), stackHigh - stackLow); |
130 | # endif |
131 | } |
132 | |
133 | #elif defined(Q_OS_DARWIN) |
134 | |
135 | StackProperties stackProperties() |
136 | { |
137 | pthread_t thread = pthread_self(); |
138 | return createStackProperties( |
139 | pthread_get_stackaddr_np(thread), |
140 | pthread_main_np() |
141 | ? getMainStackSizeFromRlimit() |
142 | : qsizetype(pthread_get_stacksize_np(thread))); |
143 | } |
144 | |
145 | #elif defined(Q_OS_WIN) |
146 | |
147 | static_assert(Q_STACK_GROWTH_DIRECTION < 0); |
148 | StackProperties stackProperties() |
149 | { |
150 | // Get the stack base. |
151 | # ifdef _WIN64 |
152 | PNT_TIB64 pTib = reinterpret_cast<PNT_TIB64>(NtCurrentTeb()); |
153 | # else |
154 | PNT_TIB pTib = reinterpret_cast<PNT_TIB>(NtCurrentTeb()); |
155 | # endif |
156 | quint8 *stackBase = reinterpret_cast<quint8 *>(pTib->StackBase); |
157 | |
158 | // Get the stack limit. tib->StackLimit is the size of the |
159 | // currently mapped stack. The address space is larger. |
160 | MEMORY_BASIC_INFORMATION mbi = {}; |
161 | if (!VirtualQuery(&mbi, &mbi, sizeof(mbi))) |
162 | qFatal("Could not retrieve memory information for stack." ); |
163 | |
164 | quint8 *stackLimit = reinterpret_cast<quint8 *>(mbi.AllocationBase); |
165 | return createStackProperties(stackBase, qsizetype(stackBase - stackLimit)); |
166 | } |
167 | |
168 | #elif defined(Q_OS_OPENBSD) |
169 | |
170 | StackProperties stackProperties() |
171 | { |
172 | // From the OpenBSD docs: |
173 | // |
174 | // The pthread_stackseg_np() function returns information about the given thread's stack. |
175 | // A stack_t is the same as a struct sigaltstack (see sigaltstack(2)) except the ss_sp |
176 | // variable points to the top of the stack instead of the base. |
177 | // |
178 | // Since the example in the sigaltstack(2) documentation shows ss_sp being assigned the result |
179 | // of a malloc() call, we can assume that "top of the stack" means "the highest address", not |
180 | // the logical top of the stack. |
181 | |
182 | stack_t ss; |
183 | rc = pthread_stackseg_np(pthread_self, &ss); |
184 | #if Q_STACK_GROWTH_DIRECTION < 0 |
185 | return createStackProperties(ss.ss_sp); |
186 | #else |
187 | return createStackProperties(decrementStackPointer(ss.ss_sp, ss.ss_size)); |
188 | #endif |
189 | } |
190 | |
191 | #elif defined(Q_OS_QNX) |
192 | |
193 | StackProperties stackProperties() |
194 | { |
195 | const auto tid = pthread_self(); |
196 | procfs_status status; |
197 | status.tid = tid; |
198 | |
199 | const int fd = open("/proc/self/ctl" , O_RDONLY); |
200 | if (fd == -1) |
201 | qFatal("Could not open /proc/self/ctl" ); |
202 | const auto guard = qScopeGuard([fd]() { close(fd); }); |
203 | |
204 | if (devctl(fd, DCMD_PROC_TIDSTATUS, &status, sizeof(status), 0) != EOK) |
205 | qFatal("Could not query thread status for current thread" ); |
206 | |
207 | if (status.tid != tid) |
208 | qFatal("Thread status query returned garbage" ); |
209 | |
210 | #if Q_STACK_GROWTH_DIRECTION < 0 |
211 | return createStackProperties( |
212 | decrementStackPointer(reinterpret_cast<void *>(status.stkbase), status.stksize), |
213 | status.stksize); |
214 | #else |
215 | return createStackProperties(reinterpret_cast<void *>(status.stkbase), status.stksize); |
216 | #endif |
217 | } |
218 | |
219 | #elif defined(Q_OS_WASM) |
220 | |
221 | StackProperties stackProperties() |
222 | { |
223 | const uintptr_t base = emscripten_stack_get_base(); |
224 | const uintptr_t end = emscripten_stack_get_end(); |
225 | const size_t size = base - end; |
226 | return createStackProperties(reinterpret_cast<void *>(base), size); |
227 | } |
228 | |
229 | #else |
230 | |
231 | StackProperties stackPropertiesGeneric(qsizetype stackSize = 0) |
232 | { |
233 | // If stackSize is given, do not trust the stack size returned by pthread_attr_getstack |
234 | |
235 | pthread_t thread = pthread_self(); |
236 | pthread_attr_t sattr; |
237 | # if defined(PTHREAD_NP_H) || defined(_PTHREAD_NP_H_) || defined(Q_OS_NETBSD) |
238 | pthread_attr_get_np(thread, &sattr); |
239 | # else |
240 | pthread_getattr_np(th: thread, attr: &sattr); |
241 | # endif |
242 | |
243 | // pthread_attr_getstack returns the address of the memory region, which is the physical |
244 | // base of the stack, not the logical one. |
245 | void *stackBase; |
246 | size_t regionSize; |
247 | int rc = pthread_attr_getstack(attr: &sattr, stackaddr: &stackBase, stacksize: ®ionSize); |
248 | pthread_attr_destroy(attr: &sattr); |
249 | |
250 | if (rc) |
251 | qFatal(msg: "Cannot find stack base" ); |
252 | |
253 | # if Q_STACK_GROWTH_DIRECTION < 0 |
254 | stackBase = decrementStackPointer(base: stackBase, amount: regionSize); |
255 | # endif |
256 | |
257 | return createStackProperties(base: stackBase, size: stackSize ? stackSize : regionSize); |
258 | } |
259 | |
260 | #if defined(Q_OS_LINUX) |
261 | |
262 | static void *stackBaseFromLibc() |
263 | { |
264 | #if defined(__GLIBC__) && QT_CONFIG(dlopen) |
265 | void **libcStackEnd = static_cast<void **>(dlsym(RTLD_DEFAULT, name: "__libc_stack_end" )); |
266 | if (!libcStackEnd) |
267 | return nullptr; |
268 | if (void *stackBase = *libcStackEnd) |
269 | return stackBase; |
270 | #endif |
271 | return nullptr; |
272 | } |
273 | |
274 | struct StackSegment { |
275 | quintptr base; |
276 | quintptr limit; |
277 | }; |
278 | |
279 | static StackSegment stackSegmentFromProc() |
280 | { |
281 | QFile maps(QStringLiteral("/proc/self/maps" )); |
282 | if (!maps.open(flags: QIODevice::ReadOnly)) |
283 | return {.base: 0, .limit: 0}; |
284 | |
285 | const quintptr stackAddr = reinterpret_cast<quintptr>(&maps); |
286 | |
287 | char buffer[1024]; |
288 | while (true) { |
289 | const qint64 length = maps.readLine(data: buffer, maxlen: 1024); |
290 | if (length <= 0) |
291 | break; |
292 | |
293 | const QByteArrayView line(buffer, length); |
294 | bool ok = false; |
295 | |
296 | const qsizetype boundary = line.indexOf(ch: '-'); |
297 | if (boundary < 0) |
298 | continue; |
299 | |
300 | const quintptr base = line.sliced(pos: 0, n: boundary).toULongLong(ok: &ok, base: 16); |
301 | if (!ok || base > stackAddr) |
302 | continue; |
303 | |
304 | const qsizetype end = line.indexOf(ch: ' ', from: boundary); |
305 | if (end < 0) |
306 | continue; |
307 | |
308 | const quintptr limit = line.sliced(pos: boundary + 1, n: end - boundary - 1).toULongLong(ok: &ok, base: 16); |
309 | if (!ok || limit <= stackAddr) |
310 | continue; |
311 | |
312 | return {.base: base, .limit: limit}; |
313 | } |
314 | |
315 | return {.base: 0, .limit: 0}; |
316 | } |
317 | |
318 | StackProperties stackProperties() |
319 | { |
320 | if (getpid() != static_cast<pid_t>(syscall(SYS_gettid))) |
321 | return stackPropertiesGeneric(); |
322 | |
323 | // On linux (including android), the pthread functions are expensive |
324 | // and unreliable on the main thread. |
325 | |
326 | // First get the stack size from rlimit |
327 | const qsizetype stackSize = getMainStackSizeFromRlimit(); |
328 | |
329 | // If we have glibc and libdl, we can query a special symbol in glibc to find the base. |
330 | // That is extremely cheap, compared to all other options. |
331 | if (stackSize) { |
332 | if (void *base = stackBaseFromLibc()) |
333 | return createStackProperties(base, size: stackSize); |
334 | } |
335 | |
336 | // Try to read the stack segment from /proc/self/maps if possible. |
337 | const StackSegment segment = stackSegmentFromProc(); |
338 | if (segment.base) { |
339 | # if Q_STACK_GROWTH_DIRECTION > 0 |
340 | void *stackBase = reinterpret_cast<void *>(segment.base); |
341 | # else |
342 | void *stackBase = reinterpret_cast<void *>(segment.limit); |
343 | # endif |
344 | return createStackProperties( |
345 | base: stackBase, size: stackSize ? stackSize : segment.limit - segment.base); |
346 | } |
347 | |
348 | // If we can't read /proc/self/maps, use the pthread functions after all, but |
349 | // override the stackSize. The main thread can grow its stack, and the pthread |
350 | // functions typically return the currently allocated stack size. |
351 | return stackPropertiesGeneric(stackSize); |
352 | } |
353 | |
354 | #else // Q_OS_LINUX |
355 | |
356 | StackProperties stackProperties() { return stackPropertiesGeneric(); } |
357 | |
358 | #endif // Q_OS_LINUX |
359 | #endif |
360 | |
361 | } // namespace QV4 |
362 | |
363 | QT_END_NAMESPACE |
364 | |