Skip to content

Commit bf03f90

Browse files
committed
Put stack first when not optimizing
Also, when not using santizers (which currently rely on using the start of memory). This allows STACK_OVERFLOW_CHECK=1 to detect stack overflow conditions better without relying on the STACK_OVERFLOW_CHECK=2 binaryen pass. This works because when stack is placed first in memory stack overflow results in SP dropping below zero which is a runtime error. We can then distinguish such runtime errors by looking at the SP value at the time of the crash. This change is part of a sequence of the effort to reduce the default stack size. The hope here is that we will be able to accurately catch overflows in debug builds even without the STACK_OVERFLOW_CHECK=2 binaryen pass, thus minimizing the impact of reducing the stack size. See #14177
1 parent a19ef66 commit bf03f90

24 files changed

+89
-34
lines changed

ChangeLog.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ See docs/process.md for more on how version tagging works.
2020

2121
3.1.25 (in development)
2222
-----------------------
23+
- In non-optimizing builds we emscripten will now place the stack first in
24+
memory, before global data. This is to get more accurate stack overflow
25+
errors (since overflow will trap rather currpting global data first). Most
26+
programs should not be effected by this change. (#18154)
2327
- The `TOTAL_STACK` setting was renamed to `STACK_SIZE`. The old name will
2428
continue to work as an alias. (#18128)
2529
- Exporting `print`/`printErr` via `-sEXPORTED_RUNTIME_METHODS` is deprecated in

emcc.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1814,6 +1814,14 @@ def phase_linker_setup(options, state, newargs):
18141814
if not settings.AUTO_JS_LIBRARIES:
18151815
default_setting('USE_SDL', 0)
18161816

1817+
if 'GLOBAL_BASE' not in user_settings and not settings.SHRINK_LEVEL and not settings.OPT_LEVEL:
1818+
# When optimizing for size it helps to put static data first before
1819+
# the stack (sincs this makes instructions for accessing this data
1820+
# use a smaller LEB encoding).
1821+
# However, for debugability is better to have the stack come first
1822+
# (becuase stack overflows will trap rather than corrupting data).
1823+
settings.STACK_FIRST = True
1824+
18171825
# Default to TEXTDECODER=2 (always use TextDecoder to decode UTF-8 strings)
18181826
# in -Oz builds, since custom decoder for UTF-8 takes up space.
18191827
# In pthreads enabled builds, TEXTDECODER==2 may not work, see
@@ -2005,7 +2013,8 @@ def phase_linker_setup(options, state, newargs):
20052013
settings.REQUIRED_EXPORTS += [
20062014
'emscripten_stack_get_end',
20072015
'emscripten_stack_get_free',
2008-
'emscripten_stack_get_base'
2016+
'emscripten_stack_get_base',
2017+
'emscripten_stack_get_current',
20092018
]
20102019

20112020
# We call one of these two functions during startup which caches the stack limits
@@ -2150,7 +2159,7 @@ def phase_linker_setup(options, state, newargs):
21502159
exit_with_error('cannot have both DYNAMIC_EXECUTION=0 and RELOCATABLE enabled at the same time, since RELOCATABLE needs to eval()')
21512160

21522161
if settings.SIDE_MODULE and 'GLOBAL_BASE' in user_settings:
2153-
exit_with_error('Cannot set GLOBAL_BASE when building SIDE_MODULE')
2162+
exit_with_error('GLOBAL_BASE is not compatible with SIDE_MODULE')
21542163

21552164
if options.use_preload_plugins or len(options.preload_files) or len(options.embed_files):
21562165
if settings.NODERAWFS:
@@ -2498,6 +2507,7 @@ def check_memory_setting(setting):
24982507
# We start our global data after the shadow memory.
24992508
# We don't need to worry about alignment here. wasm-ld will take care of that.
25002509
settings.GLOBAL_BASE = shadow_size
2510+
settings.STACK_FIRST = False
25012511

25022512
if not settings.ALLOW_MEMORY_GROWTH:
25032513
settings.INITIAL_MEMORY = total_mem

src/library.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3442,6 +3442,14 @@ mergeInto(LibraryManager.library, {
34423442
if (e instanceof ExitStatus || e == 'unwind') {
34433443
return EXITSTATUS;
34443444
}
3445+
#if STACK_OVERFLOW_CHECK
3446+
checkStackCookie();
3447+
if (e instanceof WebAssembly.RuntimeError) {
3448+
if (_emscripten_stack_get_current() <= 0) {
3449+
err('Stack overflow detected. You can try increasing -sSTACK_SIZE (currently set to ' + STACK_SIZE + ')');
3450+
}
3451+
}
3452+
#endif
34453453
#if MINIMAL_RUNTIME
34463454
throw e;
34473455
#else

src/runtime_stack_check.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ function writeStackCookie() {
1414
#if ASSERTIONS
1515
assert((max & 3) == 0);
1616
#endif
17+
// If the stack ends at address zero we write our cookies 4 bytes into the
18+
// stack. This prevents interference with the (separate) address-zero check
19+
// below.
20+
if (max == 0) {
21+
max += 4;
22+
}
1723
// The stack grow downwards towards _emscripten_stack_get_end.
1824
// We write cookies to the final two words in the stack and detect if they are
1925
// ever overwritten.
@@ -33,6 +39,9 @@ function checkStackCookie() {
3339
#if RUNTIME_DEBUG
3440
dbg('checkStackCookie: ' + max.toString(16));
3541
#endif
42+
if (max == 0) {
43+
max += 4;
44+
}
3645
var cookie1 = {{{ makeGetValue('max', 0, 'u32') }}};
3746
var cookie2 = {{{ makeGetValue('max', 4, 'u32') }}};
3847
if (cookie1 != 0x2135467 || cookie2 != 0x89BACDFE) {

src/settings_internal.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,3 +244,5 @@ var ALL_INCOMING_MODULE_JS_API = [];
244244
// when weak symbols are undefined. Only applies in the case of dyanmic linking
245245
// (MAIN_MODULE).
246246
var WEAK_IMPORTS = [];
247+
248+
var STACK_FIRST = false;

test/core/stack_overflow.cpp renamed to test/core/stack_overflow.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
#include <stdio.h>
77
#include <stdlib.h>
88
#include <stdint.h>
9-
#include <emscripten.h>
9+
#include <emscripten/emscripten.h>
10+
#include <emscripten/stack.h>
1011

1112
void recurse(unsigned long x);
1213

@@ -18,7 +19,7 @@ void act(volatile unsigned long *a) {
1819
}
1920

2021
void recurse(volatile unsigned long x) {
21-
printf("recurse %ld\n", x);
22+
printf("recurse %ld sp=%#lx\n", x, emscripten_stack_get_current());
2223
volatile unsigned long a = x;
2324
volatile char buffer[1000*1000];
2425
buffer[x/2] = 0;
@@ -36,4 +37,3 @@ void recurse(volatile unsigned long x) {
3637
int main() {
3738
recurse(1000*1000);
3839
}
39-

test/core/test_emmalloc_trim.out

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,35 @@
11
heap size: 134217728
22
dynamic heap 0: 4096
33
free dynamic memory 0: 4096
4-
unclaimed heap memory 0: 2142171136
5-
sbrk 0: 0x502000
4+
unclaimed heap memory 0: 2142175232
5+
sbrk 0: 0x501000
66
1
77
dynamic heap 1: 37752832
88
free dynamic memory 1: 4096
9-
unclaimed heap memory 1: 2104422400
10-
sbrk 1: 0x2902000
9+
unclaimed heap memory 1: 2104426496
10+
sbrk 1: 0x2901000
1111
1st trim: 1
1212
dynamic heap 1: 37752832
1313
free dynamic memory 1: 0
14-
unclaimed heap memory 1: 2104422400
15-
sbrk 1: 0x2902000
14+
unclaimed heap memory 1: 2104426496
15+
sbrk 1: 0x2901000
1616
2nd trim: 0
1717
dynamic heap 2: 37752832
1818
free dynamic memory 2: 0
19-
unclaimed heap memory 2: 2104422400
20-
sbrk 2: 0x2902000
19+
unclaimed heap memory 2: 2104426496
20+
sbrk 2: 0x2901000
2121
3rd trim: 1
2222
dynamic heap 3: 33656832
2323
free dynamic memory 3: 102400
24-
unclaimed heap memory 3: 2104422400
25-
sbrk 3: 0x2902000
24+
unclaimed heap memory 3: 2104426496
25+
sbrk 3: 0x2901000
2626
4th trim: 0
2727
dynamic heap 4: 33656832
2828
free dynamic memory 4: 102400
29-
unclaimed heap memory 4: 2104422400
30-
sbrk 4: 0x2902000
29+
unclaimed heap memory 4: 2104426496
30+
sbrk 4: 0x2901000
3131
5th trim: 1
3232
dynamic heap 5: 33558528
3333
free dynamic memory 5: 0
34-
unclaimed heap memory 5: 2104422400
35-
sbrk 5: 0x2902000
34+
unclaimed heap memory 5: 2104426496
35+
sbrk 5: 0x2901000

test/core/test_stack_placement.c

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
#include <stdio.h>
99
#include <assert.h>
10+
#include <emscripten/stack.h>
1011

1112
/* We had a regression where the stack position was not taking into account
1213
* the data and bss. This test runs with 1024 byte stack which given that bug
@@ -22,7 +23,14 @@ int main(int argc, char* argv[]) {
2223
int* bss_address = &static_bss[BSS_BYTES-1];
2324
int* stack_address = &stack_var;
2425
printf("data: %p bss: %p stack: %p\n", data_address, bss_address, stack_address);
25-
assert(stack_address > bss_address);
26+
// Stack can either come after BSS or before data (In debug builds we link
27+
// with --stack-first).
28+
int stack_first = emscripten_stack_get_end() == 0;
29+
if (stack_first) {
30+
assert(stack_address < data_address);
31+
} else {
32+
assert(stack_address > bss_address);
33+
}
2634
assert(bss_address > data_address);
2735
printf("success.\n");
2836
return 0;

test/other/metadce/test_metadce_hello_O0.exports

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ __indirect_function_table
33
__wasm_call_ctors
44
dynCall_jiji
55
emscripten_stack_get_base
6+
emscripten_stack_get_current
67
emscripten_stack_get_end
78
emscripten_stack_get_free
89
emscripten_stack_init

test/other/metadce/test_metadce_hello_O0.funcs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ $__wasi_syscall_ret
2323
$__wasm_call_ctors
2424
$dynCall_jiji
2525
$emscripten_stack_get_base
26+
$emscripten_stack_get_current
2627
$emscripten_stack_get_end
2728
$emscripten_stack_get_free
2829
$emscripten_stack_init
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
26256
1+
26641
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
12058
1+
12172

test/other/metadce/test_metadce_minimal_O0.exports

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ __indirect_function_table
33
__wasm_call_ctors
44
add
55
emscripten_stack_get_base
6+
emscripten_stack_get_current
67
emscripten_stack_get_end
78
emscripten_stack_get_free
89
emscripten_stack_init

test/other/metadce/test_metadce_minimal_O0.funcs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ $__unlockfile
88
$__wasm_call_ctors
99
$add
1010
$emscripten_stack_get_base
11+
$emscripten_stack_get_current
1112
$emscripten_stack_get_end
1213
$emscripten_stack_get_free
1314
$emscripten_stack_init
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
22987
1+
23200
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
865
1+
919
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
66886
1+
67655
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
12091
1+
12205
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
11580
1+
11644
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
65752
1+
66521
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
12091
1+
12205

test/test_core.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2949,7 +2949,7 @@ def test_bsearch(self):
29492949

29502950
def test_stack_overflow(self):
29512951
self.set_setting('ASSERTIONS', 2)
2952-
self.do_runf(test_file('core/stack_overflow.cpp'), 'Aborted(stack overflow', assert_returncode=NON_ZERO)
2952+
self.do_runf(test_file('core/stack_overflow.c'), 'Aborted(stack overflow', assert_returncode=NON_ZERO)
29532953

29542954
def test_stackAlloc(self):
29552955
self.do_core_test('stackAlloc.cpp')

test/test_other.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8597,8 +8597,8 @@ def test_js_optimizer_parse_error(self):
85978597
stderr = self.expect_fail([EMXX, 'src.cpp', '-O2'])
85988598
# wasm backend output doesn't have spaces in the EM_ASM function bodies
85998599
self.assertContained(('''
8600-
1025: () => { var x = !<->5.; }
8601-
^
8600+
5242881: () => { var x = !<->5.; }
8601+
^
86028602
'''), stderr)
86038603

86048604
def test_js_optimizer_chunk_size_determinism(self):
@@ -12674,3 +12674,10 @@ def test_em_js_deps(self):
1267412674
}
1267512675
''')
1267612676
self.do_runf('f2.c', emcc_args=['f1.c'])
12677+
12678+
def test_stack_overflow(self):
12679+
self.set_setting('STACK_OVERFLOW_CHECK', 1)
12680+
self.emcc_args += ['-O1', '--profiling-funcs']
12681+
self.do_runf(test_file('core/stack_overflow.c'),
12682+
'Stack overflow detected. You can try increasing -sSTACK_SIZE',
12683+
assert_returncode=NON_ZERO)

tools/building.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -225,8 +225,11 @@ def lld_flags_for_executable(external_symbols):
225225
cmd.append('--max-memory=%d' % settings.INITIAL_MEMORY)
226226
elif settings.MAXIMUM_MEMORY != -1:
227227
cmd.append('--max-memory=%d' % settings.MAXIMUM_MEMORY)
228-
if not settings.RELOCATABLE:
229-
cmd.append('--global-base=%s' % settings.GLOBAL_BASE)
228+
229+
if settings.STACK_FIRST:
230+
cmd.append('--stack-first')
231+
elif not settings.RELOCATABLE:
232+
cmd.append('--global-base=%s' % settings.GLOBAL_BASE)
230233

231234
return cmd
232235

0 commit comments

Comments
 (0)