From 4a15aed24887e9cbddd2527e9645bba16682e0c4 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 25 Feb 2020 16:10:57 -0800 Subject: [PATCH] -gside option: emit DWARF in a wasm on the side (#10568) When used, the main wasm file has no dwarf in it, but a .debug.wasm file on the side does. This is similar to split-dwarf in gcc/clang, but simpler. There is also a custom section in the wasm that refers to the debug file. We may evolve this further but for now it should be useful. Implemented internally using a `SIDE_DEBUG` flag. --- emcc.py | 8 ++++++++ src/settings.js | 8 ++++++++ tests/test_other.py | 15 +++++++++++++++ tools/shared.py | 22 ++++++++++++++++++++++ 4 files changed, 53 insertions(+) diff --git a/emcc.py b/emcc.py index 68ec881c37d60..7575d6cf79819 100755 --- a/emcc.py +++ b/emcc.py @@ -2819,6 +2819,11 @@ def check_bad_eq(arg): newargs[i] = '-g' shared.Settings.FULL_DWARF = 1 shared.warning('gforce_dwarf is a temporary option that will eventually disappear') + elif requested_level.startswith('side'): + # Emit full DWARF but also emit it in a file on the side + newargs[i] = '-g' + shared.Settings.FULL_DWARF = 1 + shared.Settings.SIDE_DEBUG = 1 # a non-integer level can be something like -gline-tables-only. keep # the flag for the clang frontend to emit the appropriate DWARF info. # set the emscripten debug level to 3 so that we do not remove that @@ -3337,6 +3342,9 @@ def run_closure_compiler(final): with open(final, 'w') as f: f.write(js) + if shared.Settings.FULL_DWARF and shared.Settings.SIDE_DEBUG: + shared.Building.emit_debug_on_side(wasm_binary_target) + def modularize(): global final diff --git a/src/settings.js b/src/settings.js index ad47189b30ce7..a64667db15645 100644 --- a/src/settings.js +++ b/src/settings.js @@ -1492,6 +1492,14 @@ var ELIMINATE_DUPLICATE_FUNCTIONS_PASSES = 5; // the ctors. var EVAL_CTORS = 0; +// Whether to emit DWARF in a wasm file on the side (this is not called +// "split"/"separate" because there is already a DWARF concept by that name). +// When DWARF is on the side, the main file has no DWARF info, while the side +// file, ending in .debug.wasm, has the same wasm binary + all the debug +// sections. +// This has no effect if DWARF is not being emitted. +var SIDE_DEBUG = 0; + // see http://kripken.github.io/emscripten-site/docs/debugging/CyberDWARF.html // [fastcomp-only] var CYBERDWARF = 0; diff --git a/tests/test_other.py b/tests/test_other.py index 3bd09786c94de..c535be850c01a 100644 --- a/tests/test_other.py +++ b/tests/test_other.py @@ -9033,6 +9033,21 @@ def test(infile, source_map_added_dir=''): ensure_dir('inner') test('inner/a.cpp', 'inner') + @no_fastcomp('dwarf') + def test_side_debug(self): + run_process([PYTHON, EMCC, path_from_root('tests', 'hello_world.c'), '-gforce_dwarf']) + self.assertExists('a.out.wasm') + self.assertNotExists('a.out.wasm.debug.wasm') + run_process([PYTHON, EMCC, path_from_root('tests', 'hello_world.c'), '-gside']) + self.assertExists('a.out.wasm') + self.assertExists('a.out.wasm.debug.wasm') + self.assertLess(os.path.getsize('a.out.wasm'), os.path.getsize('a.out.wasm.debug.wasm')) + # the special section should also exist, that refers to the side debug file + with open('a.out.wasm', 'rb') as f: + wasm = f.read() + self.assertIn(b'external_debug_info', wasm) + self.assertIn(b'a.out.wasm.debug.wasm', wasm) + def test_wasm_producers_section(self): # no producers section by default run_process([PYTHON, EMCC, path_from_root('tests', 'hello_world.c')]) diff --git a/tools/shared.py b/tools/shared.py index b7c1c38828cbf..f61b24baf695e 100644 --- a/tools/shared.py +++ b/tools/shared.py @@ -704,6 +704,7 @@ def replace_or_append_suffix(filename, new_suffix): LLVM_INTERPRETER = os.path.expanduser(build_llvm_tool_path(exe_suffix('lli'))) LLVM_COMPILER = os.path.expanduser(build_llvm_tool_path(exe_suffix('llc'))) LLVM_DWARFDUMP = os.path.expanduser(build_llvm_tool_path(exe_suffix('llvm-dwarfdump'))) +LLVM_OBJCOPY = os.path.expanduser(build_llvm_tool_path(exe_suffix('llvm-objcopy'))) WASM_LD = os.path.expanduser(build_llvm_tool_path(exe_suffix('wasm-ld'))) EMSCRIPTEN = path_from_root('emscripten.py') @@ -2756,6 +2757,27 @@ def wasm2js(js_file, wasm_file, opt_level, minify_whitespace, use_closure_compil f.write(all_js) return js_file + @staticmethod + def emit_debug_on_side(wasm_file): + # extract the DWARF info from the main file, and leave the wasm with + # debug into as a file on the side + # TODO: emit only debug sections in the side file, and not the entire + # wasm as well + wasm_file_with_dwarf = wasm_file + '.debug.wasm' + shutil.move(wasm_file, wasm_file_with_dwarf) + run_process([LLVM_OBJCOPY, '--remove-section=.debug*', wasm_file_with_dwarf, wasm_file]) + + # embed a section in the main wasm to point to the file with external DWARF, + # see https://yurydelendik.github.io/webassembly-dwarf/#external-DWARF + section_name = b'\x13external_debug_info' # section name, including prefixed size + contents = asbytes(wasm_file_with_dwarf) + section_size = len(section_name) + len(contents) + with open(wasm_file, 'ab') as f: + f.write(b'\0') # user section is code 0 + f.write(WebAssembly.lebify(section_size)) + f.write(section_name) + f.write(contents) + @staticmethod def apply_wasm_memory_growth(js_file): logger.debug('supporting wasm memory growth with pthreads')