Skip to content
7 changes: 7 additions & 0 deletions ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ See docs/process.md for more on how version tagging works.

4.0.15 (in development)
-----------------------
- The `-gsource-map` flag has been updated to be independent of other types of
debugging effects (in particular it no longer causes the wasm binary to have
a name section, and it no longer suppresses minification of the JS output).
To get the previous behavior, add `-g2` along with `-gsource-map`.
See also the newly updated
[documentation](https://emscripten.org/docs/porting/Debugging.html) which
covers debugging flags and use cases (#25238).

4.0.14 - 09/02/25
-----------------
Expand Down
2 changes: 1 addition & 1 deletion test/test_browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ def test_sdl1_es6(self):
self.reftest('hello_world_sdl.c', 'htmltest.png', cflags=['-sUSE_SDL', '-lGL', '-sEXPORT_ES6'])

def test_emscripten_log(self):
self.btest_exit('test_emscripten_log.cpp', cflags=['-Wno-deprecated-pragma', '-gsource-map'])
self.btest_exit('test_emscripten_log.cpp', cflags=['-Wno-deprecated-pragma', '-gsource-map', '-g2'])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this needed in the test now? If so maybe add a comment why? (I assume the source map is needed for logging, but not sure why -g2)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test expects both names and source lines in the output.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, thanks, so -g2 is for the names section.


@also_with_wasmfs
def test_preload_file(self):
Expand Down
20 changes: 15 additions & 5 deletions test/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -8978,28 +8978,38 @@ def test_sanitize_vptr(self):
"which does not point to an object of type 'R'",
])

# The sanitizer runtime can symbolize based on dwarf, a name section, a sourcemap,
# or a combination.
@parameterized({
'g': ('-g', [
'g': (['-g'], [
".cpp:3:12: runtime error: reference binding to null pointer of type 'int'",
'in main',
]),
'g4': ('-gsource-map', [
'g2': (['-g2'], [
".cpp:3:12: runtime error: reference binding to null pointer of type 'int'",
'in main',
]),
'gsource_map': (['-gsource-map'], [
".cpp:3:12: runtime error: reference binding to null pointer of type 'int'",
'.cpp:3:8',
]),
'g4': (['-gsource-map', '-g2'], [
".cpp:3:12: runtime error: reference binding to null pointer of type 'int'",
'in main ',
'.cpp:3:8',
]),
})
@no_wasm2js('TODO: sanitizers in wasm2js')
@no_esm_integration('sanitizers do not support WASM_ESM_INTEGRATION')
def test_ubsan_full_stack_trace(self, g_flag, expected_output):
if g_flag == '-gsource-map':
def test_ubsan_full_stack_trace(self, g_flags, expected_output):
if '-gsource-map' in g_flags:
if self.is_wasm2js():
self.skipTest('wasm2js has no source map support')
elif self.get_setting('EVAL_CTORS'):
self.skipTest('EVAL_CTORS does not support source maps')

create_file('pre.js', 'Module.UBSAN_OPTIONS = "print_stacktrace=1";')
self.cflags += ['-fsanitize=null', g_flag, '--pre-js=pre.js']
self.cflags += ['-fsanitize=null', '--pre-js=pre.js'] + g_flags
self.set_setting('ALLOW_MEMORY_GROWTH')
self.do_runf('core/test_ubsan_full_null_ref.cpp',
assert_all=True, expected_output=expected_output)
Expand Down
25 changes: 15 additions & 10 deletions test/test_other.py
Original file line number Diff line number Diff line change
Expand Up @@ -3145,21 +3145,23 @@ def test_dwarf_sourcemap_names(self):
(['-O1', '-g'], True, False, True),
(['-O3', '-g'], True, False, True),
(['-gsplit-dwarf'], True, False, True),
# TODO: It seems odd that -gsource-map leaves behind a name section. Should it?
(['-gsource-map'], False, True, True),
(['-g1', '-Oz', '-gsource-map'], False, True, True),
(['-gsource-map'], False, True, False),
(['-g2', '-gsource-map'], False, True, True),
(['-g1', '-Oz', '-gsource-map'], False, True, False),
(['-gsource-map', '-g0'], False, False, False),
# --emit-symbol-map should not affect the results
(['--emit-symbol-map', '-gsource-map'], False, True, True),
(['--emit-symbol-map', '-gsource-map'], False, True, False),
(['--emit-symbol-map'], False, False, False),
(['--emit-symbol-map', '-Oz'], False, False, False),
(['-sASYNCIFY=1', '-g0'], False, False, False),
(['-sASYNCIFY=1', '-gsource-map'], False, True, True),
(['-sASYNCIFY=1', '-gsource-map'], False, True, False),
(['-sASYNCIFY=1', '-gsource-map', '-g2'], False, True, True),
(['-g', '-gsource-map'], True, True, True),
(['-g2', '-gsource-map'], False, True, True),
(['-gsplit-dwarf', '-gsource-map'], True, True, True),
(['-gsource-map', '-sERROR_ON_WASM_CHANGES_AFTER_LINK'], False, True, True),
(['-Oz', '-gsource-map'], False, True, True),
(['-Oz', '-gsource-map'], False, True, False),
(['-gsource-map', '-sERROR_ON_WASM_CHANGES_AFTER_LINK'], False, True, False),
(['-gsource-map', '-Og', '-sERROR_ON_WASM_CHANGES_AFTER_LINK'], False, True, False),
]:
print(flags, expect_dwarf, expect_sourcemap, expect_names)
self.emcc(test_file(source_file), flags, js_file)
Expand Down Expand Up @@ -9250,19 +9252,22 @@ def test_binaryen_debug(self):
for args, expect_clean_js, expect_whitespace_js, expect_closured in [
(['-O0'], False, True, False),
(['-O0', '-g1'], False, True, False),
(['-O0', '-g2'], False, True, False), # in -g2+, we emit -g to asm2wasm so function names are saved
(['-O0', '-g2'], False, True, False),
(['-O0', '-g'], False, True, False),
(['-O0', '--profiling-funcs'], False, True, False),
(['-O0', '-gline-tables-only'], False, True, False),
(['-O1'], False, True, False),
(['-O3'], True, False, False),
(['-Oz', '-gsource-map'], False, True, False), # TODO: fix this (#20462)
(['-Oz', '-gsource-map'], True, False, False),
(['-O2'], True, False, False),
(['-O2', '-gz'], True, False, False), # -gz means debug compression, it should not enable debugging
(['-O2', '-g1'], False, True, False),
(['-O2', '-g'], False, True, False),
(['-O2', '--closure=1'], True, False, True),
(['-O2', '--closure=1', '-g1'], True, True, True),
(['-O2', '--minify=0'], False, True, False),
(['-O2', '--profiling-funcs'], True, False, False),
(['-O2', '--profiling'], False, True, False),
]:
print(args, expect_clean_js, expect_whitespace_js, expect_closured)
delete_file('a.out.wat')
Expand Down Expand Up @@ -12383,7 +12388,7 @@ def test_lsan_leaks(self, ext, args):
def test_lsan_stack_trace(self, ext, regexes):
self.do_runf(
'other/test_lsan_leaks.' + ext,
cflags=['-fsanitize=leak', '-gsource-map'],
cflags=['-fsanitize=leak', '-gsource-map', '-g2'],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same question.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same answer: the test expects both names and line numbers.

regex=True,
assert_all=True,
assert_returncode=NON_ZERO,
Expand Down
2 changes: 1 addition & 1 deletion tools/building.py
Original file line number Diff line number Diff line change
Expand Up @@ -1232,7 +1232,7 @@ def run_binaryen_command(tool, infile, outfile=None, args=None, debug=False, std
# we must tell binaryen to update it
# TODO: all tools should support source maps; wasm-ctor-eval does not atm,
# for example
if settings.GENERATE_SOURCE_MAP and outfile and tool in ['wasm-opt', 'wasm-emscripten-finalize']:
if settings.GENERATE_SOURCE_MAP and outfile and tool in ['wasm-opt', 'wasm-emscripten-finalize', 'wasm-metadce']:
cmd += [f'--input-source-map={infile}.map']
cmd += [f'--output-source-map={outfile}.map']
shared.print_compiler_stage(cmd)
Expand Down
10 changes: 4 additions & 6 deletions tools/cmdline.py
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,8 @@ def consume_arg_file():
settings.EMIT_NAME_SECTION = 1
# if we don't need to preserve LLVM debug info, do not keep this flag
# for clang
if settings.DEBUG_LEVEL < 3:
if (settings.DEBUG_LEVEL < 3 and not
(settings.GENERATE_SOURCE_MAP or settings.SEPARATE_DWARF)):
Copy link
Member

@aheejin aheejin Sep 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does something about settings.SEPARATE_DWARF has to change here? (What about split-dwarf and normal dwarf cases? Don't we need the same DEBUG_LEVEL for them?)

And has settings.SEPARATE_DWARF been set by this point? It looks we parse -gseparate-dwarf in line 385 and set settings.SEPARATE_DWARF in line 394 and 396.

Copy link
Member Author

@dschuff dschuff Sep 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, this logic is really tricky. Basically if you have -gsource-map and -g2 together at compile time (to get SM and names), then you set settings.GENERATE_SOURCE_MAP when you parse -gsource-map, and then when you parse -g2 it sets newargs[i] = '-g0' which turns off debug info entirely for the compile and breaks source maps.

Similarly with separate-dwarf which has the side effect of enabling debug info (rather than purely specifying that dwarf should be separated).

This change keeps that from happening, but I also think the fact that -g2 rounds to -g0 at compile time is a bug to fix in a future PR.

Copy link
Member

@aheejin aheejin Sep 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if I understand. And -gsource-map and -gseparate-dwarf are parsed below this line (-gsoure-map in line 399, -gseparate-dwarf in line 385) and settings.GENERATE_SOURCE_MAP and settings.SEPARATE_DWARF are set there, so I'm not sure how we can query these here. If this passes all tests that probably means I'm mistaken though.

Also where do we process -gsplit-dwarf?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the problem is when you have both flags you make 2 trips through this loop with gsource-map first. the first time it sets DEBUG_LEVEL to 3 and then the second time it sets it back to 2 and sets the 'g0' flag to clang, meaning no debug info at all.
-gsplit-dwarf falls into the else case at line 405.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah thanks, I forgot this code was within a loop...

newargs[i] = '-g0'
else:
# for 3+, report -g3 to clang as -g4 etc. are not accepted
Expand All @@ -394,9 +395,9 @@ def consume_arg_file():
else:
settings.SEPARATE_DWARF = True
settings.GENERATE_DWARF = 1
settings.DEBUG_LEVEL = 3
elif requested_level in ['source-map', 'source-map=inline']:
settings.GENERATE_SOURCE_MAP = 1 if requested_level == 'source-map' else 2
settings.EMIT_NAME_SECTION = 1
newargs[i] = '-g'
elif requested_level == 'z':
# Ignore `-gz`. We don't support debug info compression.
Expand All @@ -407,10 +408,7 @@ def consume_arg_file():
# clang and make the emscripten code treat it like any other DWARF.
settings.GENERATE_DWARF = 1
settings.EMIT_NAME_SECTION = 1
# In all cases set the emscripten debug level to 3 so that we do not
# strip during link (during compile, this does not make a difference).
# TODO: possibly decouple some of these flags from the final debug level (#20462)
settings.DEBUG_LEVEL = 3
settings.DEBUG_LEVEL = 3
elif check_flag('-profiling') or check_flag('--profiling'):
settings.DEBUG_LEVEL = max(settings.DEBUG_LEVEL, 2)
settings.EMIT_NAME_SECTION = 1
Expand Down