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