1/*
2 * Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved.
3 * Copyright (C) 2007 Eric Seidel <[email protected]>
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18 *
19 */
20
21#include "config.h"
22#include <wtf/StackBounds.h>
23
24#include <mutex>
25#include <wtf/NoTailCalls.h>
26
27#if OS(DARWIN)
28
29#include <mach/task.h>
30#include <mach/thread_act.h>
31#include <pthread.h>
32
33#elif OS(WINDOWS)
34
35#include <windows.h>
36
37#elif OS(UNIX)
38
39#include <pthread.h>
40#if HAVE(PTHREAD_NP_H)
41#include <pthread_np.h>
42#endif
43
44#endif
45
46namespace WTF {
47
48#if CPU(X86) || CPU(X86_64) || CPU(ARM) || CPU(ARM64) || CPU(MIPS)
49ALWAYS_INLINE StackBounds::StackDirection StackBounds::stackDirection()
50{
51 return StackDirection::Downward;
52}
53#else
54static NEVER_INLINE NOT_TAIL_CALLED StackBounds::StackDirection testStackDirection2(volatile const uint8_t* pointer)
55{
56 volatile uint8_t* stackValue = bitwise_cast<uint8_t*>(currentStackPointer());
57 return (pointer < stackValue) ? StackBounds::StackDirection::Upward : StackBounds::StackDirection::Downward;
58}
59
60static NEVER_INLINE NOT_TAIL_CALLED StackBounds::StackDirection testStackDirection()
61{
62 NO_TAIL_CALLS();
63 volatile uint8_t* stackValue = bitwise_cast<uint8_t*>(currentStackPointer());
64 return testStackDirection2(stackValue);
65}
66
67NEVER_INLINE StackBounds::StackDirection StackBounds::stackDirection()
68{
69 static StackBounds::StackDirection result = StackBounds::StackDirection::Downward;
70 static std::once_flag onceKey;
71 std::call_once(onceKey, [] {
72 NO_TAIL_CALLS();
73 result = testStackDirection();
74 });
75 return result;
76}
77#endif
78
79#if OS(DARWIN)
80
81StackBounds StackBounds::newThreadStackBounds(PlatformThreadHandle thread)
82{
83 ASSERT(stackDirection() == StackDirection::Downward);
84 void* origin = pthread_get_stackaddr_np(thread);
85 rlim_t size = pthread_get_stacksize_np(thread);
86 void* bound = static_cast<char*>(origin) - size;
87 return StackBounds { origin, bound };
88}
89
90StackBounds StackBounds::currentThreadStackBoundsInternal()
91{
92 ASSERT(stackDirection() == StackDirection::Downward);
93 if (pthread_main_np()) {
94 // FIXME: <rdar://problem/13741204>
95 // pthread_get_size lies to us when we're the main thread, use get_rlimit instead
96 void* origin = pthread_get_stackaddr_np(pthread_self());
97 rlimit limit;
98 getrlimit(RLIMIT_STACK, &limit);
99 rlim_t size = limit.rlim_cur;
100 void* bound = static_cast<char*>(origin) - size;
101 return StackBounds { origin, bound };
102 }
103 return newThreadStackBounds(pthread_self());
104}
105
106#elif OS(UNIX)
107
108#if OS(OPENBSD)
109
110StackBounds StackBounds::newThreadStackBounds(PlatformThreadHandle thread)
111{
112 stack_t stack;
113 pthread_stackseg_np(thread, &stack);
114 void* origin = stack.ss_sp;
115 void* bound = nullptr;
116 if (stackDirection() == StackDirection::Upward)
117 bound = static_cast<char*>(origin) + stack.ss_size;
118 else
119 bound = static_cast<char*>(origin) - stack.ss_size;
120 return StackBounds { origin, bound };
121}
122
123#else // !OS(OPENBSD)
124
125StackBounds StackBounds::newThreadStackBounds(PlatformThreadHandle thread)
126{
127 void* bound = nullptr;
128 size_t stackSize = 0;
129
130 pthread_attr_t sattr;
131 pthread_attr_init(&sattr);
132#if HAVE(PTHREAD_NP_H) || OS(NETBSD)
133 // e.g. on FreeBSD 5.4, [email protected]
134 pthread_attr_get_np(thread, &sattr);
135#else
136 // FIXME: this function is non-portable; other POSIX systems may have different np alternatives
137 pthread_getattr_np(thread, &sattr);
138#endif
139 int rc = pthread_attr_getstack(&sattr, &bound, &stackSize);
140 UNUSED_PARAM(rc);
141 ASSERT(bound);
142 pthread_attr_destroy(&sattr);
143 void* origin = static_cast<char*>(bound) + stackSize;
144 // pthread_attr_getstack's bound is the lowest accessible pointer of the stack.
145 // If stack grows up, origin and bound in this code should be swapped.
146 if (stackDirection() == StackDirection::Upward)
147 std::swap(origin, bound);
148
149 return StackBounds { origin, bound };
150}
151
152#endif // OS(OPENBSD)
153
154StackBounds StackBounds::currentThreadStackBoundsInternal()
155{
156 return newThreadStackBounds(pthread_self());
157}
158
159#elif OS(WINDOWS)
160
161StackBounds StackBounds::currentThreadStackBoundsInternal()
162{
163 ASSERT(stackDirection() == StackDirection::Downward);
164 MEMORY_BASIC_INFORMATION stackOrigin { };
165 VirtualQuery(&stackOrigin, &stackOrigin, sizeof(stackOrigin));
166 // stackOrigin.AllocationBase points to the reserved stack memory base address.
167
168 void* origin = static_cast<char*>(stackOrigin.BaseAddress) + stackOrigin.RegionSize;
169 // The stack on Windows consists out of three parts (uncommitted memory, a guard page and present
170 // committed memory). The 3 regions have different BaseAddresses but all have the same AllocationBase
171 // since they are all from the same VirtualAlloc. The 3 regions are laid out in memory (from high to
172 // low) as follows:
173 //
174 // High |-------------------| -----
175 // | committedMemory | ^
176 // |-------------------| |
177 // | guardPage | reserved memory for the stack
178 // |-------------------| |
179 // | uncommittedMemory | v
180 // Low |-------------------| ----- <--- stackOrigin.AllocationBase
181 //
182 // See http://msdn.microsoft.com/en-us/library/ms686774%28VS.85%29.aspx for more information.
183
184 MEMORY_BASIC_INFORMATION uncommittedMemory;
185 VirtualQuery(stackOrigin.AllocationBase, &uncommittedMemory, sizeof(uncommittedMemory));
186 ASSERT(uncommittedMemory.State == MEM_RESERVE);
187
188 MEMORY_BASIC_INFORMATION guardPage;
189 VirtualQuery(static_cast<char*>(uncommittedMemory.BaseAddress) + uncommittedMemory.RegionSize, &guardPage, sizeof(guardPage));
190 ASSERT(guardPage.Protect & PAGE_GUARD);
191
192 void* endOfStack = stackOrigin.AllocationBase;
193
194#ifndef NDEBUG
195 MEMORY_BASIC_INFORMATION committedMemory;
196 VirtualQuery(static_cast<char*>(guardPage.BaseAddress) + guardPage.RegionSize, &committedMemory, sizeof(committedMemory));
197 ASSERT(committedMemory.State == MEM_COMMIT);
198
199 void* computedEnd = static_cast<char*>(origin) - (uncommittedMemory.RegionSize + guardPage.RegionSize + committedMemory.RegionSize);
200
201 ASSERT(stackOrigin.AllocationBase == uncommittedMemory.AllocationBase);
202 ASSERT(stackOrigin.AllocationBase == guardPage.AllocationBase);
203 ASSERT(stackOrigin.AllocationBase == committedMemory.AllocationBase);
204 ASSERT(stackOrigin.AllocationBase == uncommittedMemory.BaseAddress);
205 ASSERT(endOfStack == computedEnd);
206#endif // NDEBUG
207 void* bound = static_cast<char*>(endOfStack) + guardPage.RegionSize;
208 return StackBounds { origin, bound };
209}
210
211#else
212#error Need a way to get the stack bounds on this platform
213#endif
214
215} // namespace WTF
216