Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SIDE_DEBUG option: emit DWARF in a wasm on the side #10568

Merged
merged 8 commits into from
Feb 26, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions emcc.py
Original file line number Diff line number Diff line change
Expand Up @@ -2821,6 +2821,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'):
Copy link
Member

Choose a reason for hiding this comment

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

Does this still suffer from the problem that -g after -gside negates the SIDE_DEBUG setting?
Actually looking at the code, I don't really see how that happens. FULL_DWARF will get set to 1, but then nothing unsets it when the -g is later processed?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, that flag mixup was something else I think. @pfaffe let me know if that's still an issue and if I can help there.

Overall -g can't override the two new flags we added, so this should be ok.

# 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
Expand Down Expand Up @@ -3339,6 +3344,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
Expand Down
8 changes: 8 additions & 0 deletions src/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
15 changes: 15 additions & 0 deletions tests/test_other.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')])
Expand Down
22 changes: 22 additions & 0 deletions tools/shared.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

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

This bit isn't necessary. The section header is created by objcopy. We only need the payload here.

Copy link
Member

Choose a reason for hiding this comment

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

(the alternative of course would be to create the header here too and then just directly append to the file rather than using objcopy at all)

Copy link
Member Author

Choose a reason for hiding this comment

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

This doesn't use objcopy to add the new section - it just appends the bytes itself manually, so we emit all the parts. (We do similar things for a few other custom sections, like the dylink section, see make_shared_library().)

Copy link
Member

Choose a reason for hiding this comment

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

oh oops, I didn't read it closely enough :D

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')
Expand Down