Qt 6.x
The Qt SDK
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Pages
qv4stacklimits.cpp
Go to the documentation of this file.
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
39
40namespace QV4 {
41
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,
49#elif defined(Q_OS_MACOS)
50 PlatformStackSize = 8 * 1024 * 1024,
52#elif defined(Q_OS_ANDROID)
53 // Android appears to have 1MB stacks.
54 PlatformStackSize = 1024 * 1024,
56#elif defined(Q_OS_LINUX)
57 // On linux, we assume 8MB stacks if rlimit doesn't work.
58 PlatformStackSize = 8 * 1024 * 1024,
60#elif defined(Q_OS_QNX)
61 // QNX's stack is only 512k by default
62 PlatformStackSize = 512 * 1024,
64#else
65 // We try to claim 512k if we don't know anything else.
66 PlatformStackSize = 512 * 1024,
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
75template<typename Size>
76static 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
85template<typename Size>
86static 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
96{
97 return StackProperties {
98 base,
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.
111static qsizetype getMainStackSizeFromRlimit()
112{
113 rlimit limit;
114 return (getrlimit(RLIMIT_STACK, &limit) == 0 && limit.rlim_cur != RLIM_INFINITY)
115 ? qsizetype(limit.rlim_cur)
117}
118#endif
119
120#if defined(Q_OS_INTEGRITY)
121
122StackProperties 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
135StackProperties stackProperties()
136{
137 pthread_t thread = pthread_self();
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
147static_assert(Q_STACK_GROWTH_DIRECTION < 0);
148StackProperties 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
170StackProperties 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
193StackProperties 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
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
221StackProperties 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
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 pthread_attr_init(&sattr);
238# if defined(PTHREAD_NP_H) || defined(_PTHREAD_NP_H_) || defined(Q_OS_NETBSD)
239 pthread_attr_get_np(thread, &sattr);
240# else
241 pthread_getattr_np(thread, &sattr);
242# endif
243
244 // pthread_attr_getstack returns the address of the memory region, which is the physical
245 // base of the stack, not the logical one.
246 void *stackBase;
247 size_t regionSize;
248 int rc = pthread_attr_getstack(&sattr, &stackBase, &regionSize);
249 pthread_attr_destroy(&sattr);
250
251 if (rc)
252 qFatal("Cannot find stack base");
253
254# if Q_STACK_GROWTH_DIRECTION < 0
255 stackBase = decrementStackPointer(stackBase, regionSize);
256# endif
257
258 return createStackProperties(stackBase, stackSize ? stackSize : regionSize);
259}
260
261#if defined(Q_OS_LINUX)
262
263static void *stackBaseFromLibc()
264{
265#if defined(__GLIBC__) && QT_CONFIG(dlopen)
266 void **libcStackEnd = static_cast<void **>(dlsym(RTLD_DEFAULT, "__libc_stack_end"));
267 if (!libcStackEnd)
268 return nullptr;
269 if (void *stackBase = *libcStackEnd)
270 return stackBase;
271#endif
272 return nullptr;
273}
274
275struct StackSegment {
278};
279
280static StackSegment stackSegmentFromProc()
281{
282 QFile maps(QStringLiteral("/proc/self/maps"));
283 if (!maps.open(QIODevice::ReadOnly))
284 return {0, 0};
285
286 const quintptr stackAddr = reinterpret_cast<quintptr>(&maps);
287
288 char buffer[1024];
289 while (true) {
290 const qint64 length = maps.readLine(buffer, 1024);
291 if (length <= 0)
292 break;
293
295 bool ok = false;
296
297 const qsizetype boundary = line.indexOf('-');
298 if (boundary < 0)
299 continue;
300
301 const quintptr base = line.sliced(0, boundary).toULongLong(&ok, 16);
302 if (!ok || base > stackAddr)
303 continue;
304
305 const qsizetype end = line.indexOf(' ', boundary);
306 if (end < 0)
307 continue;
308
309 const quintptr limit = line.sliced(boundary + 1, end - boundary - 1).toULongLong(&ok, 16);
310 if (!ok || limit <= stackAddr)
311 continue;
312
313 return {base, limit};
314 }
315
316 return {0, 0};
317}
318
319StackProperties stackProperties()
320{
321 if (getpid() != static_cast<pid_t>(syscall(SYS_gettid)))
322 return stackPropertiesGeneric();
323
324 // On linux (including android), the pthread functions are expensive
325 // and unreliable on the main thread.
326
327 // First get the stack size from rlimit
328 const qsizetype stackSize = getMainStackSizeFromRlimit();
329
330 // If we have glibc and libdl, we can query a special symbol in glibc to find the base.
331 // That is extremely cheap, compared to all other options.
332 if (stackSize) {
333 if (void *base = stackBaseFromLibc())
334 return createStackProperties(base, stackSize);
335 }
336
337 // Try to read the stack segment from /proc/self/maps if possible.
338 const StackSegment segment = stackSegmentFromProc();
339 if (segment.base) {
340# if Q_STACK_GROWTH_DIRECTION > 0
341 void *stackBase = reinterpret_cast<void *>(segment.base);
342# else
343 void *stackBase = reinterpret_cast<void *>(segment.limit);
344# endif
346 stackBase, stackSize ? stackSize : segment.limit - segment.base);
347 }
348
349 // If we can't read /proc/self/maps, use the pthread functions after all, but
350 // override the stackSize. The main thread can grow its stack, and the pthread
351 // functions typically return the currently allocated stack size.
352 return stackPropertiesGeneric(stackSize);
353}
354
355#else // Q_OS_LINUX
356
358
359#endif // Q_OS_LINUX
360#endif
361
362} // namespace QV4
363
\inmodule QtCore
Definition qfile.h:93
qulonglong toULongLong(bool *ok=nullptr, int base=10) const
Returns the string converted to an {unsigned long long} using base base, which is 10 by default and m...
QString sliced(qsizetype pos) const
Definition qstring.h:341
static QString static QString qsizetype indexOf(QChar c, qsizetype from=0, Qt::CaseSensitivity cs=Qt::CaseSensitive) const
Definition qstring.cpp:4420
Combined button and popup list for selecting options.
\qmltype Particle \inqmlmodule QtQuick.Particles
static void * incrementStackPointer(void *base, Size amount)
static void * decrementStackPointer(void *base, Size amount)
@ PlatformStackSize
@ DefaultSafetyMargin
@ PlatformSafetyMargin
static StackProperties createStackProperties(void *base, qsizetype size=PlatformStackSize)
StackProperties stackProperties()
StackProperties stackPropertiesGeneric(qsizetype stackSize=0)
#define Size(name)
#define qFatal
Definition qlogging.h:164
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLuint GLuint end
GLenum GLuint GLenum GLsizei length
GLenum GLuint buffer
GLuint64 GLenum GLint fd
GLuint segment
GLint limit
QScopeGuard< typename std::decay< F >::type > qScopeGuard(F &&f)
[qScopeGuard]
Definition qscopeguard.h:60
#define QStringLiteral(str)
size_t quintptr
Definition qtypes.h:72
ptrdiff_t qsizetype
Definition qtypes.h:70
long long qint64
Definition qtypes.h:55
unsigned char quint8
Definition qtypes.h:41
#define Q_STACK_GROWTH_DIRECTION
file open(QIODevice::ReadOnly)