فهرست منبع

addr2line.py: Support sourceMappingURL section produced by emcc (#3302)

And update the debug-tools sample.
liang.he 1 سال پیش
والد
کامیت
fef26ead3e

+ 2 - 2
.github/workflows/compilation_on_android_ubuntu.yml

@@ -389,14 +389,14 @@ jobs:
           cd /opt
           sudo wget ${{ matrix.wasi_sdk_release }}
           sudo tar -xzf wasi-sdk-*.tar.gz
-          sudo mv wasi-sdk-20.0 wasi-sdk
+          sudo ln -sf wasi-sdk-20.0 wasi-sdk
 
       - name: download and install wabt
         run: |
           cd /opt
           sudo wget ${{ matrix.wabt_release }}
           sudo tar -xzf wabt-1.0.31-*.tar.gz
-          sudo mv wabt-1.0.31 wabt
+          sudo ln -sf wabt-1.0.31 wabt
       - name: Get LLVM libraries
         id: retrieve_llvm_libs
         uses: actions/cache@v4

+ 4 - 4
.github/workflows/compilation_on_macos.yml

@@ -273,14 +273,14 @@ jobs:
           cd /opt
           sudo wget ${{ matrix.wasi_sdk_release }}
           sudo tar -xzf wasi-sdk-*.tar.gz
-          sudo mv wasi-sdk-20.0 wasi-sdk
+          sudo ln -sf wasi-sdk-20.0 wasi-sdk
 
       - name: download and install wabt
         run: |
           cd /opt
           sudo wget ${{ matrix.wabt_release }}
           sudo tar -xzf wabt-1.0.31-*.tar.gz
-          sudo mv wabt-1.0.31 wabt
+          sudo ln -sf wabt-1.0.31 wabt
 
       - name: Build Sample [basic]
         run: |
@@ -346,7 +346,7 @@ jobs:
           cmake ..
           cmake --build . --config Release --parallel 4
         working-directory: wamr-compiler
-        
+
       - name: Build Sample [wasi-threads]
         run: |
           cd samples/wasi-threads
@@ -378,4 +378,4 @@ jobs:
           cmake --build . --config Debug --parallel 4
           ./iwasm wasm-apps/trap.wasm | grep "#" > call_stack.txt
           ./iwasm wasm-apps/trap.aot | grep "#" > call_stack_aot.txt
-          bash -x ../symbolicate.sh  
+          bash -x ../symbolicate.sh

+ 6 - 6
.github/workflows/nightly_run.yml

@@ -8,7 +8,7 @@ on:
     types:
       - opened
       - synchronize
-    # running nightly pipeline if you're changing it 
+    # running nightly pipeline if you're changing it
     # stress tests are run only in nightly at the moment, so running them in they are changed
     paths:
       - ".github/workflows/nightly_run.yml"
@@ -54,7 +54,7 @@ jobs:
     with:
       os: "ubuntu-22.04"
       arch: "X86"
-  
+
   build_wamrc:
     needs:
       [
@@ -65,7 +65,7 @@ jobs:
       matrix:
         include:
           - os: ubuntu-20.04
-            llvm_cache_key: ${{ needs.build_llvm_libraries_on_ubuntu_2004.outputs.cache_key }}  
+            llvm_cache_key: ${{ needs.build_llvm_libraries_on_ubuntu_2004.outputs.cache_key }}
     steps:
       - name: checkout
         uses: actions/checkout@v4
@@ -459,13 +459,13 @@ jobs:
           cd /opt
           sudo wget ${{ matrix.wasi_sdk_release }}
           sudo tar -xzf wasi-sdk-*.tar.gz
-          sudo mv wasi-sdk-20.0 wasi-sdk
+          sudo ln -sf wasi-sdk-20.0 wasi-sdk
       - name: download and install wabt
         run: |
           cd /opt
           sudo wget ${{ matrix.wabt_release }}
           sudo tar -xzf wabt-1.0.31-*.tar.gz
-          sudo mv wabt-1.0.31 wabt
+          sudo ln -sf wabt-1.0.31 wabt
 
       - name: Get LLVM libraries
         id: retrieve_llvm_libs
@@ -643,7 +643,7 @@ jobs:
           sudo tar -xzf wasi-sdk-*.tar.gz
           sudo mv wasi-sdk-20.0 wasi-sdk
 
-      # It is a temporary solution until new wasi-sdk that includes bug fixes is released 
+      # It is a temporary solution until new wasi-sdk that includes bug fixes is released
       - name: build wasi-libc from source
         if: matrix.test_option == '$WASI_TEST_OPTIONS'
         run: |

+ 32 - 1
samples/debug-tools/CMakeLists.txt

@@ -7,6 +7,14 @@ include(CheckPIESupported)
 
 project(debug_tools_sample)
 
+list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)
+find_package(WASISDK REQUIRED)
+
+option(SOURCE_MAP_DEMO "Enable source map demo" OFF)
+if (SOURCE_MAP_DEMO)
+  find_package(EMSCRIPTEN 3.1.50 REQUIRED)
+endif ()
+
 ################  runtime settings  ################
 string (TOLOWER ${CMAKE_HOST_SYSTEM_NAME} WAMR_BUILD_PLATFORM)
 if (APPLE)
@@ -61,7 +69,30 @@ include (${WAMR_ROOT_DIR}/build-scripts/runtime_lib.cmake)
 add_library(vmlib ${WAMR_RUNTIME_LIB_SOURCE})
 
 ################ wasm application ################
-add_subdirectory(wasm-apps)
+include(ExternalProject)
+
+# wasm32-wasi
+ExternalProject_Add(wasm33-wasi
+  SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/wasm-apps"
+  CONFIGURE_COMMAND ${CMAKE_COMMAND} -S ${CMAKE_CURRENT_SOURCE_DIR}/wasm-apps -B build
+                      -DWASI_SDK_PREFIX=${WASISDK_HOME}
+                      -DCMAKE_TOOLCHAIN_FILE=${WASISDK_TOOLCHAIN}
+  BUILD_COMMAND     ${CMAKE_COMMAND} --build build
+  INSTALL_COMMAND   ${CMAKE_COMMAND} --install build --prefix ${CMAKE_CURRENT_BINARY_DIR}
+)
+
+if (EMSCRIPTEN_FOUND)
+  # wasm32-emscripten
+  ExternalProject_Add(wasm32-emscripten
+    SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/wasm-apps"
+    CONFIGURE_COMMAND ${CMAKE_COMMAND} -S ${CMAKE_CURRENT_SOURCE_DIR}/wasm-apps -B build
+                        -DCMAKE_TOOLCHAIN_FILE=${EMSCRIPTEN_TOOLCHAIN}
+                        -DCMAKE_VERBOSE_MAKEFILE=On
+                        -DSOURCE_MAP_DEMO=On
+    BUILD_COMMAND     ${CMAKE_COMMAND} --build build
+    INSTALL_COMMAND   ${CMAKE_COMMAND} --install build --prefix ${CMAKE_CURRENT_BINARY_DIR}/emscripten
+  )
+endif ()
 
 ################ wamr runtime ################
 include (${SHARED_DIR}/utils/uncommon/shared_uncommon.cmake)

+ 34 - 1
samples/debug-tools/README.md

@@ -80,6 +80,39 @@ $ python3 ../../../test-tools/addr2line/addr2line.py \
     call_stack.txt --no-addr
 ```
 
+#### sourcemap
+
+This script also supports _sourcemap_ which is produced by [_emscripten_](https://emscripten.org/docs/tools_reference/emcc.html). The _sourcemap_ is used to map the wasm function to the original source file. To use it, add `-gsource-map` option to _emcc_ command line. The output should be a section named "sourceMappingURL" and a separated file named "_.map_.
+
+If the wasm file is with _sourcemap_, the script will use it to get the source file and line info. It needs an extra command line option `--emsdk` to specify the path of _emsdk_. The script will use _emsymbolizer_ to query the source file and line info.
+
+````bash
+$ python3 ../../../test-tools/addr2line/addr2line.py \
+    --wasi-sdk /opt/wasi-sdk \
+    --wabt /opt/wabt \
+    --wasm-file emscripten/wasm-apps/trap.wasm \
+    --emsdk /opt/emsdk \
+    call_stack.from_wasm_w_sourcemap.txt
+
+The output should be something like:
+
+```text
+1: c
+        at ../../../../../wasm-apps/trap.c:5:1
+2: b
+        at ../../../../../wasm-apps/trap.c:11:12
+3: a
+        at ../../../../../wasm-apps/trap.c:17:12
+4: main
+        at ../../../../../wasm-apps/trap.c:24:5
+5: __main_void
+        at ../../../../../../../../../emsdk/emscripten/system/lib/standalone/__main_void.c:53:10
+6: _start
+        at ../../../../../../../../../emsdk/emscripten/system/lib/libc/crt1.c:27:3
+````
+
+> The script assume the separated map file _.map_ is in the same directory as the wasm file.
+
 ### Another approach
 
 If the wasm file is with "name" section, it is able to output function name in the stack trace. To achieve that, need to enable `WAMR_BUILD_LOAD_CUSTOM_SECTION` and `WAMR_BUILD_CUSTOM_NAME_SECTION`. If using .aot file, need to add `--emit-custom-sections=name` into wamrc command line options.
@@ -97,4 +130,4 @@ Then the output should be something like
 Exception: unreachable
 ```
 
-Also, it is able to use *addr2line.py* to add file and line info to the stack trace.
+Also, it is able to use _addr2line.py_ to add file and line info to the stack trace.

+ 45 - 0
samples/debug-tools/cmake/FindEMSCRIPTEN.cmake

@@ -0,0 +1,45 @@
+# Copyright (C) 2019 Intel Corporation. All rights reserved.
+# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+include(FindPackageHandleStandardArgs)
+
+find_path(EMSCRIPTEN_HOME
+  NAMES upstream/emscripten
+  PATHS /opt/emsdk
+  NO_DEFAULT_PATH
+  NO_CMAKE_PATH
+  NO_CMAKE_SYSTEM_PATH
+  NO_CMAKE_FIND_ROOT_PATH
+  REQUIRED
+)
+
+find_file(EMSCRIPTEN_VERSION_FILE
+  NAMES emscripten-version.txt
+  PATHS ${EMSCRIPTEN_HOME}/upstream/emscripten
+  NO_DEFAULT_PATH
+  NO_CMAKE_PATH
+  NO_CMAKE_SYSTEM_PATH
+  NO_CMAKE_FIND_ROOT_PATH
+  REQUIRED
+)
+
+file(READ ${EMSCRIPTEN_VERSION_FILE} EMSCRIPTEN_VERSION_FILE_CONTENT)
+
+string(REGEX
+    MATCH
+    "[0-9]+\.[0-9]+(\.[0-9]+)*"
+    EMSCRIPTEN_VERSION
+    ${EMSCRIPTEN_VERSION_FILE_CONTENT}
+)
+
+find_package_handle_standard_args(EMSCRIPTEN
+  REQUIRED_VARS EMSCRIPTEN_HOME
+  VERSION_VAR EMSCRIPTEN_VERSION
+  HANDLE_VERSION_RANGE
+)
+
+if(EMSCRIPTEN_FOUND)
+  set(EMSCRIPTEN_TOOLCHAIN  ${EMSCRIPTEN_HOME}/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake)
+  set(EMCC ${EMSCRIPTEN_HOME}/upstream/emscripten/emcc)
+endif()
+mark_as_advanced(EMSCRIPTEN_TOOLCHAIN EMCC)

+ 27 - 0
samples/debug-tools/cmake/FindWAMRC.cmake

@@ -0,0 +1,27 @@
+# Copyright (C) 2019 Intel Corporation. All rights reserved.
+# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+include(FindPackageHandleStandardArgs)
+
+find_path(WAMRC_HOME
+  wamr-compiler
+  PATHS ${CMAKE_CURRENT_SOURCE_DIR}/../../..
+  NO_DEFAULT_PATH
+  NO_CMAKE_PATH
+  NO_CMAKE_SYSTEM_PATH
+  NO_CMAKE_FIND_ROOT_PATH
+  REQUIRED
+)
+
+find_file(WAMRC_BIN
+  wamrc
+  HINTS ${WAMRC_HOME}/wamr-compiler/build
+  NO_DEFAULT_PATH
+  NO_CMAKE_PATH
+  NO_CMAKE_SYSTEM_PATH
+  NO_CMAKE_FIND_ROOT_PATH
+  REQUIRED
+)
+
+find_package_handle_standard_args(WAMRC REQUIRED_VARS WAMRC_BIN)
+mark_as_advanced(WAMRC_BIN)

+ 24 - 0
samples/debug-tools/cmake/FindWASISDK.cmake

@@ -0,0 +1,24 @@
+# Copyright (C) 2019 Intel Corporation. All rights reserved.
+# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+include(FindPackageHandleStandardArgs)
+
+file(GLOB WASISDK_SEARCH_PATH "/opt/wasi-sdk-*")
+find_path(WASISDK_HOME
+  NAMES share/wasi-sysroot
+  PATHS ${WASISDK_SEARCH_PATH}
+  NO_DEFAULT_PATH
+  REQUIRED
+)
+
+string(REGEX MATCH [0-9]+\.[0-9]+\.*[0-9]* WASISDK_VERSION ${WASISDK_HOME})
+
+find_package_handle_standard_args(WASISDK REQUIRED_VARS WASISDK_HOME VERSION_VAR WASISDK_VERSION)
+
+if(WASISDK_FOUND)
+  set(WASISDK_CC_COMMAND  ${WASISDK_HOME}/bin/clang)
+  set(WASISDK_CXX_COMMAND ${WASISDK_HOME}/bin/clang++)
+  set(WASISDK_TOOLCHAIN   ${WASISDK_HOME}/share/cmake/wasi-sdk.cmake)
+  set(WASISDK_SYSROOT     ${WASISDK_HOME}/share/wasi-sysroot)
+endif()
+mark_as_advanced(WASISDK_CC_COMMAND WASISDK_CXX_COMMAND WASISDK_TOOLCHAIN WASISDK_SYSROOT WASISDK_HOME)

+ 41 - 74
samples/debug-tools/wasm-apps/CMakeLists.txt

@@ -1,91 +1,58 @@
 # Copyright (C) 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 
-if (APPLE)
-    set (HAVE_FLAG_SEARCH_PATHS_FIRST 0)
-    set (CMAKE_C_LINK_FLAGS "")
-    set (CMAKE_CXX_LINK_FLAGS "")
-endif ()
+cmake_minimum_required (VERSION 3.14)
 
-if (NOT DEFINED WASI_SDK_DIR)
-  set (WASI_SDK_DIR               "/opt/wasi-sdk")
-endif ()
+project (debut_tools_wasm)
 
-if (DEFINED WASI_SYSROOT)
-  set (CMAKE_SYSROOT                "${WASI_SYSROOT}")
-endif ()
+set (CMAKE_BUILD_TYPE Debug)  # Otherwise no debug symbols (addr2line)
 
-set (CMAKE_C_COMPILER               "${WASI_SDK_DIR}/bin/clang")
-set (CMAKE_ASM_COMPILER               "${WASI_SDK_DIR}/bin/clang")
-set (CMAKE_EXE_LINKER_FLAGS         "-target wasm32-wasi")
-
-################ wabt and wamrc dependencies ################
-message(CHECK_START "Detecting WABT")
-if(NOT (DEFINED WABT_DIR OR DEFINED CACHE{WABT_DIR}))
-  find_path(WABT_DIR
-    wabt
-    PATHS /opt
-    NO_DEFAULT_PATH
-    NO_CMAKE_FIND_ROOT_PATH
-  )
-  if(DEFINED WABT_DIR)
-    set(WABT_DIR ${WABT_DIR}/wabt)
-  endif()
-endif()
-if(WABT_DIR)
-  message(CHECK_PASS "found")
-else()
-  message(CHECK_FAIL "not found")
-endif()
+list (APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/../cmake)
+find_package (WAMRC REQUIRED)
 
-message(CHECK_START "Detecting WASM_OBJDUMP at ${WABT_DIR}")
-find_program(WASM_OBJDUMP
-  wasm-objdump
-  PATHS "${WABT_DIR}/bin"
-  NO_DEFAULT_PATH
-  NO_CMAKE_FIND_ROOT_PATH
-)
-if(WASM_OBJDUMP)
-  message(CHECK_PASS "found")
-else()
-  message(CHECK_FAIL "not found")
-endif()
-if((NOT EXISTS ${WASM_OBJDUMP}) )
-  message(FATAL_ERROR "Please make sure to have wasm-objdump under the path=${WABT_DIR}/bin ")
-endif()
-
-set(WAMR_COMPILER_DIR ${CMAKE_CURRENT_LIST_DIR}/../../wamr-compiler/build)
-message(CHECK_START "Detecting WAMR_COMPILER at ${WAMR_COMPILER_DIR}")
-find_file(WAMR_COMPILER
-  wamrc
-  PATHS "${CMAKE_CURRENT_LIST_DIR}/../../../wamr-compiler/build"
-  NO_DEFAULT_PATH
-  NO_CMAKE_FIND_ROOT_PATH
-)
-if(WAMR_COMPILER)
-  message(CHECK_PASS "found")
-else()
-  message(CHECK_FAIL "not found")
-endif()
-if((NOT EXISTS ${WAMR_COMPILER}) )
-  message(FATAL_ERROR "Please build wamrc under the path=${WAMR_ROOT_DIR}/wamr-compiler/")
-endif()
+option(SOURCE_MAP_DEMO "Enable source map demo" OFF)
+if (SOURCE_MAP_DEMO)
+  find_package(EMSCRIPTEN 3.1.50 REQUIRED)
+endif ()
 
 ################ wasm and aot compilation ################
 function (compile_sample SOURCE_FILE)
   get_filename_component (FILE_NAME ${SOURCE_FILE} NAME_WLE)
-  set (WASM_MODULE ${FILE_NAME}.wasm)
-  add_executable (${WASM_MODULE} ${SOURCE_FILE})
 
-  add_custom_target(
-    wasm_to_aot
+  ## wasm
+  set (WASM_FILE ${FILE_NAME}.wasm)
+  add_executable (${FILE_NAME} ${SOURCE_FILE})
+  set_target_properties (${FILE_NAME} PROPERTIES SUFFIX .wasm)
+
+  ## aot
+  set (AOT_FILE ${FILE_NAME}.aot)
+  add_custom_target (
+    ${FILE_NAME}_aot
     ALL
-    DEPENDS ${WAMR_COMPILER} ${WASM_MODULE}
+    DEPENDS ${WAMRC_BIN} ${WASM_FILE}
     # Use --enable-dump-call-stack to generate stack trace (addr2line)
-    COMMAND ${WAMR_COMPILER} --size-level=0 --enable-dump-call-stack -o wasm-apps/trap.aot wasm-apps/trap.wasm
-    WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+    COMMAND ${WAMRC_BIN} --size-level=0 --enable-dump-call-stack -o ${AOT_FILE} ${WASM_FILE}
+    WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
   )
+
+  ## wasm + sourcemap
+  if (DEFINED EMSCRIPTEN)
+    add_custom_target(
+      ${FILE_NAME}_w_sourcemap
+      ALL
+      DEPENDS ${SOURCE_FILE}
+      COMMAND ${EMCC} -O0 -gsource-map -o ${FILE_NAME}.sourcemap.wasm ${CMAKE_CURRENT_SOURCE_DIR}/${SOURCE_FILE}
+      WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+    )
+  endif ()
+
+  ## install both
+  install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${WASM_FILE} DESTINATION wasm-apps)
+  install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${AOT_FILE}  DESTINATION wasm-apps)
+  if (DEFINED EMSCRIPTEN)
+    install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${FILE_NAME}.sourcemap.wasm DESTINATION wasm-apps)
+    install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${FILE_NAME}.sourcemap.wasm.map DESTINATION wasm-apps)
+  endif ()
 endfunction ()
 
-set(CMAKE_BUILD_TYPE Debug)  # Otherwise no debug symbols (addr2line)
-compile_sample(trap.c)
+compile_sample(trap.c)

+ 106 - 19
test-tools/addr2line/addr2line.py

@@ -43,6 +43,28 @@ For example, there is a call-stack dump:
 """
 
 
+def locate_sourceMappingURL_section(wasm_objdump: Path, wasm_file: Path) -> bool:
+    """
+    Figure out if the wasm file has a sourceMappingURL section.
+    """
+    cmd = f"{wasm_objdump} -h {wasm_file}"
+    p = subprocess.run(
+        shlex.split(cmd),
+        check=True,
+        capture_output=True,
+        text=True,
+        universal_newlines=True,
+    )
+    outputs = p.stdout.split(os.linesep)
+
+    for line in outputs:
+        line = line.strip()
+        if "sourceMappingURL" in line:
+            return True
+
+    return False
+
+
 def get_code_section_start(wasm_objdump: Path, wasm_file: Path) -> int:
     """
     Find the start offset of Code section in a wasm file.
@@ -62,15 +84,6 @@ def get_code_section_start(wasm_objdump: Path, wasm_file: Path) -> int:
     )
     outputs = p.stdout.split(os.linesep)
 
-    # if there is no .debug section, return -1
-    for line in outputs:
-        line = line.strip()
-        if ".debug_info" in line:
-            break
-    else:
-        print(f"No .debug_info section found {wasm_file}")
-        return -1
-
     for line in outputs:
         line = line.strip()
         if "Code" in line:
@@ -79,7 +92,7 @@ def get_code_section_start(wasm_objdump: Path, wasm_file: Path) -> int:
     return -1
 
 
-def get_line_info_from_function_addr(
+def get_line_info_from_function_addr_dwarf(
     dwarf_dump: Path, wasm_file: Path, offset: int
 ) -> tuple[str, str, str, str]:
     """
@@ -126,7 +139,7 @@ def get_dwarf_tag_value(tag: str, line: str) -> str:
     return m.groups()[0]
 
 
-def get_line_info_from_function_name(
+def get_line_info_from_function_name_dwarf(
     dwarf_dump: Path, wasm_file: Path, function_name: str
 ) -> tuple[str, str, str]:
     """
@@ -160,6 +173,51 @@ def get_line_info_from_function_name(
     return (function_name, function_file, function_line)
 
 
+def get_line_info_from_function_addr_sourcemapping(
+    emsymbolizer: Path, wasm_file: Path, offset: int
+) -> tuple[str, str, str, str]:
+    """
+    Find the location info of a given offset in a wasm file which is compiled with emcc.
+
+    {emsymbolizer} {wasm_file} {offset of file}
+
+    there usually are two lines:
+    ??
+    relative path to source file:line:column
+    """
+    debug_info_source = wasm_file.with_name(f"{wasm_file.name}.map")
+    cmd = f"{emsymbolizer} -t code -f {debug_info_source} {wasm_file} {offset}"
+    p = subprocess.run(
+        shlex.split(cmd),
+        check=False,
+        capture_output=True,
+        text=True,
+        universal_newlines=True,
+        cwd=Path.cwd(),
+    )
+    outputs = p.stdout.split(os.linesep)
+
+    function_name, function_file = "<unknown>", "unknown"
+    function_line, function_column = "?", "?"
+
+    for line in outputs:
+        line = line.strip()
+
+        if not line:
+            continue
+
+        m = re.match("(.*):(\d+):(\d+)", line)
+        if m:
+            function_file, function_line, function_column = m.groups()
+            continue
+        else:
+            # it's always ??, not sure about that
+            if "??" != line:
+                function_name = line
+
+    return (function_name, function_file, function_line, function_column)
+
+
 def parse_line_info(line_info: str) -> tuple[str, str, str]:
     """
     line_info -> [file, line, column]
@@ -250,6 +308,7 @@ def main():
         action="store_true",
         help="use call stack without addresses or from fast interpreter mode",
     )
+    parser.add_argument("--emsdk", type=Path, help="path to emsdk")
     args = parser.parse_args()
 
     wasm_objdump = args.wabt.joinpath("bin/wasm-objdump")
@@ -261,6 +320,15 @@ def main():
     llvm_cxxfilt = args.wasi_sdk.joinpath("bin/llvm-cxxfilt")
     assert llvm_cxxfilt.exists()
 
+    emcc_production = locate_sourceMappingURL_section(wasm_objdump, args.wasm_file)
+    if emcc_production:
+        if args.emsdk is None:
+            print("Please provide the path to emsdk via --emsdk")
+            return -1
+
+        emsymbolizer = args.emsdk.joinpath("upstream/emscripten/emsymbolizer")
+        assert emsymbolizer.exists()
+
     code_section_start = get_code_section_start(wasm_objdump, args.wasm_file)
     if code_section_start == -1:
         return -1
@@ -281,6 +349,7 @@ def main():
 
             _, offset, index = splitted
             if args.no_addr:
+                # FIXME: w/ emcc production
                 if not index.startswith("$f"):  # E.g. _start or Text format
                     print(f"{i}: {index}")
                     continue
@@ -290,22 +359,40 @@ def main():
                     print(f"{i}: {line}")
                     continue
 
-                line_info = get_line_info_from_function_name(
-                    llvm_dwarf_dump, args.wasm_file, function_index_to_name[index]
-                )
+                if not emcc_production:
+                    _, function_file, function_line = (
+                        get_line_info_from_function_name_dwarf(
+                            llvm_dwarf_dump,
+                            args.wasm_file,
+                            function_index_to_name[index],
+                        )
+                    )
+                else:
+                    _, function_file, function_line = _, "unknown", "?"
 
-                _, function_file, function_line = line_info
                 function_name = demangle(llvm_cxxfilt, function_index_to_name[index])
                 print(f"{i}: {function_name}")
                 print(f"\tat {function_file}:{function_line}")
             else:
                 offset = int(offset, 16)
+                # match the algorithm in wasm_interp_create_call_stack()
+                # either a *offset* to *code* section start
+                # or a *offset* in a file
+                assert offset > code_section_start
                 offset = offset - code_section_start
-                function_name, function_file, function_line, function_column = (
-                    get_line_info_from_function_addr(
-                        llvm_dwarf_dump, args.wasm_file, offset
+
+                if emcc_production:
+                    function_name, function_file, function_line, function_column = (
+                        get_line_info_from_function_addr_sourcemapping(
+                            emsymbolizer, args.wasm_file, offset
+                        )
+                    )
+                else:
+                    function_name, function_file, function_line, function_column = (
+                        get_line_info_from_function_addr_dwarf(
+                            llvm_dwarf_dump, args.wasm_file, offset
+                        )
                     )
-                )
 
                 # if can't parse function_name, use name section or <index>
                 if function_name == "<unknown>":