Skip to content
Closed
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
2 changes: 1 addition & 1 deletion .github/workflows/build-and-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ jobs:
cflags: "-m32 -O3"
otp: "23"
elixir_version: "1.11"
cmake_opts: "-DOPENSSL_CRYPTO_LIBRARY=/usr/lib/i386-linux-gnu/libcrypto.so"
cmake_opts: "-DOPENSSL_CRYPTO_LIBRARY=/usr/lib/i386-linux-gnu/libcrypto.so -DAVM_CREATE_STACKTRACES=off"
arch: "i386"
compiler_pkgs: "gcc-10 g++-10 gcc-10-multilib g++-10-multilib libc6-dev-i386
libc6-dbg:i386 zlib1g-dev:i386 libssl-dev:i386"
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added support for `erlang:integer_to_list/2` and `erlang:integer_to_binary/2`
- Added functions `esp:sleep_enable_ext0_wakeup/2` and `esp:sleep_enable_ext1_wakeup/2.`
- Added support for FP opcodes 94-102 thus removing the need for `AVM_DISABLE_FP=On` with OTP-22+
- Added support for stacktraces

### Fixed
- Fixed issue with formatting integers with io:format() on STM32 platform
Expand Down
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ find_package(Elixir)
option(AVM_DISABLE_FP "Disable floating point support." OFF)
option(AVM_USE_32BIT_FLOAT "Use 32 bit floats." OFF)
option(AVM_VERBOSE_ABORT "Print module and line number on VM abort" OFF)
option(AVM_RELEASE "Build an AtomVM release" OFF)
option(AVM_CREATE_STACKTRACES "Create stacktraces" ON)
option(COVERAGE "Build for code coverage" OFF)

if((${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") OR
Expand Down
16 changes: 14 additions & 2 deletions CMakeModules/BuildElixir.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,16 @@ macro(pack_archive avm_name)
DEPENDS ${BEAMS}
)

if(AVM_RELEASE)
set(INCLUDE_LINES "")
else()
set(INCLUDE_LINES "-i")
endif()

add_custom_target(
${avm_name} ALL
DEPENDS ${avm_name}_beams PackBEAM
COMMAND ${CMAKE_BINARY_DIR}/tools/packbeam/PackBEAM -a ${avm_name}.avm ${BEAMS}
COMMAND ${CMAKE_BINARY_DIR}/tools/packbeam/PackBEAM -a ${INCLUDE_LINES} ${avm_name}.avm ${BEAMS}
COMMENT "Packing archive ${avm_name}.avm"
VERBATIM
)
Expand All @@ -64,6 +70,12 @@ macro(pack_runnable avm_name main)
DEPENDS Elixir.${main}.beam
)

if(AVM_RELEASE)
set(INCLUDE_LINES "")
else()
set(INCLUDE_LINES "-i")
endif()

foreach(archive_name ${ARGN})
if(${archive_name} STREQUAL "exavmlib")
set(ARCHIVES ${ARCHIVES} ${CMAKE_BINARY_DIR}/libs/${archive_name}/lib/${archive_name}.avm)
Expand All @@ -75,7 +87,7 @@ macro(pack_runnable avm_name main)

add_custom_target(
${avm_name} ALL
COMMAND ${CMAKE_BINARY_DIR}/tools/packbeam/PackBEAM ${avm_name}.avm Elixir.${main}.beam ${ARCHIVES}
COMMAND ${CMAKE_BINARY_DIR}/tools/packbeam/PackBEAM ${INCLUDE_LINES} ${avm_name}.avm Elixir.${main}.beam ${ARCHIVES}
COMMENT "Packing runnable ${avm_name}.avm"
VERBATIM
)
Expand Down
32 changes: 28 additions & 4 deletions CMakeModules/BuildErlang.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,17 @@ macro(pack_archive avm_name)
DEPENDS ${BEAMS}
)

if(AVM_RELEASE)
set(INCLUDE_LINES "")
else()
set(INCLUDE_LINES "-i")
endif()

add_custom_target(
${avm_name} ALL
DEPENDS ${avm_name}_beams PackBEAM
#DEPENDS ${BEAMS}
COMMAND ${CMAKE_BINARY_DIR}/tools/packbeam/PackBEAM -a ${avm_name}.avm ${BEAMS}
COMMAND ${CMAKE_BINARY_DIR}/tools/packbeam/PackBEAM -a ${INCLUDE_LINES} ${avm_name}.avm ${BEAMS}
COMMENT "Packing archive ${avm_name}.avm"
VERBATIM
)
Expand All @@ -60,9 +66,15 @@ macro(pack_lib avm_name)
set(ARCHIVE_TARGETS ${ARCHIVE_TARGETS} ${archive_name})
endforeach()

if(AVM_RELEASE)
set(INCLUDE_LINES "")
else()
set(INCLUDE_LINES "-i")
endif()

add_custom_target(
${avm_name} ALL
COMMAND ${CMAKE_BINARY_DIR}/tools/packbeam/PackBEAM -a ${avm_name}.avm ${ARCHIVES}
COMMAND ${CMAKE_BINARY_DIR}/tools/packbeam/PackBEAM -a ${INCLUDE_LINES} ${avm_name}.avm ${ARCHIVES}
COMMENT "Packing runnable ${avm_name}.avm"
VERBATIM
)
Expand Down Expand Up @@ -98,9 +110,15 @@ macro(pack_runnable avm_name main)
set(ARCHIVE_TARGETS ${ARCHIVE_TARGETS} ${archive_name})
endforeach()

if(AVM_RELEASE)
set(INCLUDE_LINES "")
else()
set(INCLUDE_LINES "-i")
endif()

add_custom_target(
${avm_name} ALL
COMMAND ${CMAKE_BINARY_DIR}/tools/packbeam/PackBEAM ${avm_name}.avm ${main}.beam ${ARCHIVES}
COMMAND ${CMAKE_BINARY_DIR}/tools/packbeam/PackBEAM ${INCLUDE_LINES} ${avm_name}.avm ${main}.beam ${ARCHIVES}
COMMENT "Packing runnable ${avm_name}.avm"
VERBATIM
)
Expand All @@ -118,10 +136,16 @@ macro(pack_test test_avm_name)
set(ARCHIVE_TARGETS ${ARCHIVE_TARGETS} ${archive_name})
endforeach()

if(AVM_RELEASE)
set(INCLUDE_LINES "")
else()
set(INCLUDE_LINES "-i")
endif()

add_custom_target(
${test_avm_name} ALL
COMMAND erlc -I ${CMAKE_SOURCE_DIR}/libs/include ${CMAKE_CURRENT_SOURCE_DIR}/tests.erl
COMMAND ${CMAKE_BINARY_DIR}/tools/packbeam/PackBEAM ${CMAKE_CURRENT_BINARY_DIR}/${test_avm_name}.avm ${CMAKE_CURRENT_BINARY_DIR}/tests.beam ${ARCHIVES}
COMMAND ${CMAKE_BINARY_DIR}/tools/packbeam/PackBEAM ${INCLUDE_LINES} ${CMAKE_CURRENT_BINARY_DIR}/${test_avm_name}.avm ${CMAKE_CURRENT_BINARY_DIR}/tests.beam ${ARCHIVES}
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/tests.erl
COMMENT "Packing runnable ${test_avm_name}.avm"
VERBATIM
Expand Down
38 changes: 38 additions & 0 deletions doc/src/atomvm-internals.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,41 @@ The relationship between the `GlobalContext` fields that manage BEAM processes a
## Exception Handling

## The Scheduler

## Stacktraces

Stacktraces are computed from information gathered at load time from BEAM modules loaded into the application, together with information in the runtime stack that is maintained during the execution of a program. In addition, if a BEAM file contains a `Line` chunk, additional information is added to stack traces, including the file name (as defined at compile time), as well as the line number of a function call.

> Note. Adding line information to a BEAM file adds non-trivial memory overhead to applications and should only be used when necessary (e.g., during the development process). For applications to make the best use of memory in tightly constrained environments, packagers should consider removing line information all together from BEAM files and rely instead on logging or other mechanisms for diagnosing problems in the field.

Newcomers to Erlang may find stacktraces slightly confusing, because some optimizations taken by the Erlang compiler and runtime can result in stack frames "missing" from stack traces. For example, tail-recursive function calls, as well as function calls that occur as the last expression in a function clause, don't involve the creation of frames in the runtime stack, and consequently will not appear in a stacktrace.

### Line Numbers

Including file and line number information in stacktraces adds considerable overhead to both the BEAM file data, as well as the memory consumed at module load time. The data structures used to track line numbers and file names are described below and are only created if the associated BEAM file contains a `Line` chunk.

#### The line-refs table

The line-refs table is an array of 16-bit integers, mapping line references (as they occur in BEAM instructions) to the actual line numbers in a file. (Internally, BEAM instructions do not reference line numbers directly, but instead are indirected through a line index). This table is stored on the `Module` structure.

This table is populated when the BEAM file is loaded. The table is created from information in the `Line` chunk in the BEAM file, if it exists. Note that if there is no `Line` chunk in a BEAM file, this table is not created.

The memory cost of this table is `num_line_refs * 2` bytes, for each loaded module, or 0, if there is no `Line` chunk in the associated BEAM file.

#### The filenames table

The filenames table is a table of (usually only 1?) file name. This table maps filename indices to `ModuleFilename` structures, which is essentially a pointer and a length (of type `size_t`). This table generally only contains 1 entry, the file name of the Erlang source code module from which the BEAM file was generated. This table is stored on the `Module` structure.

Note that a `ModuleFilename` structure points to data directly in the `Line` chunk of the BEAM file. Therefore, for ports of AtomVM that memory-map BEAM file data (e.g., ESP32), the actual file name data does not consume any memory.

The memory cost of this table is `num_filenames * sizeof(struct ModuleFilename)`, where `struct ModuleFilename` is a pointer and length, for each loaded module, or 0, if there is no `Line` chunk in the associated BEAM file.

#### The line-ref-offsets list

The line-ref-offsets list is a sequence of `LineRefOffset` structures, where each structure contains a ListHead (for list book-keeping), a 16-bit line-ref, and an unsigned integer value designating the code offset at which the line reference occurs in the code chunk of the BEAM file. This list is stored on the `Module` structure.

This list is populated at code load time. When a line reference is encountered during code loading, a `LineRefOffset` structure is allocated and added to the line-ref-offsets list. This list is used at a later time to find the line number at which a stack frame is called, in a manner described below.

The memory cost of this list is `num_line_refs * sizeof(struct LineRefOffset)`, for each loaded module, or 0, if there is no `Line` chunk in the associated BEAM file.

### Raw Stacktraces
44 changes: 44 additions & 0 deletions doc/src/programmers-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ The `PackBEAM` tool is a command-line application that can be used to create Pac
shell$ PackBEAM -h
Usage: PackBEAM [-h] [-l] <avm-file> [<options>]
-h Print this help menu.
-i Include file and line information.
-l <input-avm-file> List the contents of an AVM file.
[-a] <output-avm-file> <input-beam-or-avm-file>+ Create an AVM file (archive if -a specified).

Expand Down Expand Up @@ -387,6 +388,49 @@ Use `base64:encode/1` and `base64:decode/1` to encode to and decode from Base64

You can Use `base64:encode_to_string/1` and `base64:decode_to_string/1` to perform the same encoding, but to return values as Erlang list structures, instead of as binaries.

### StackTraces

You can obtain information about the current state of a process via stacktraces, which provide information about the location of function calls (possibly including file names and line numbers) in your program.

Currently in AtomVM, stack traces can be obtained in one of following ways:

* via try-catch blocks
* via catch blocks, when an error has been raised via the `error` Bif.

> Note. AtomVM does not support `erlang:get_stacktrace/0` which was deprecated in Erlang/OTP 21 and 22, stopped working in Erlang/OTP 23 and was removed in Erlang/OTP 24. Support for accessing the current stacktrace via `erlang:process_info/2` may be added in the future.

For example a stack trace can be bound to a variable in the catch clause in a try-catch block:

try
do_something()
catch
_Class:_Error:Stacktrace ->
io:format("Stacktrace: ~p~n", [Stacktrace])
end

Alternatively, a stack trace can be bound to the result of a `catch` expression, but only when the error is raised by the `error` Bif. For example,

{'EXIT', {foo, Stacktrace}} = (catch error(foo)),
io:format("Stacktrace: ~p~n", [Stacktrace])

Stack traces are printed to the console in a crash report, for example, when a process dies unexpectedly.

Stacktrace data is represented as a list of tuples, each of which represents a stack “frame”. Each tuple is of the form:

[{Module :: module(), Function :: atom(), Arity :: non_neg_integer(), AuxData :: aux_data()}]

where `aux_data()` is a (possibly empty) properties list containing the following elements:

[{file, File :: string(), line, Line :: pos_integer()}]

Stack frames are ordered from the frame “closest“ to the point of failure (the “top” of the stack) to the frame furthest from the point of failure (the “bottom” of the stack).

Stack frames will contain file and line information in the AuxData list if the BEAM files (typically embedded in AVM files) include <<“Line”>> chunks generated by the compiler. Otherwise, the AuxData will be an empty list.

> Note. Adding line information to BEAM files not only increases the size of BEAM files in storage, but calculation of file and line information can have a non-negligible impact on memory usage. Memory-sensitive applications should consider not including line information in BEAM files.

The PackBEAM tool does not include file and line information in the AVM files it creates, but file and line information can be included via a command line option. For information about the PackBEAM too, see the [`PackBEAM` tool](#Packbeam_tool).

### Math

AtomvVM supports the following standard functions from the OTP `math` module:
Expand Down
6 changes: 6 additions & 0 deletions src/libAtomVM/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ set(HEADER_FILES
port.h
refc_binary.h
scheduler.h
stacktrace.h
sys.h
term_typedef.h
term.h
Expand Down Expand Up @@ -81,6 +82,7 @@ set(SOURCE_FILES
port.c
refc_binary.c
scheduler.c
stacktrace.c
term.c
timer_wheel.c
valueshashtable.c
Expand Down Expand Up @@ -127,6 +129,10 @@ if (AVM_VERBOSE_ABORT)
target_compile_definitions(libAtomVM PUBLIC AVM_VERBOSE_ABORT)
endif()

if(AVM_CREATE_STACKTRACES)
target_compile_definitions(libAtomVM PUBLIC AVM_CREATE_STACKTRACES)
endif()

# Automatically use zlib if present to load .beam files
if (${CMAKE_SYSTEM_NAME} STREQUAL "Darwin" OR ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD")
find_package(ZLIB)
Expand Down
3 changes: 3 additions & 0 deletions src/libAtomVM/iff.c
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ void scan_iff(const void *iff_binary, int buf_size, unsigned long *offsets, unsi
} else if (!memcmp(current_record->name, "StrT", 4)) {
offsets[STRT] = current_pos;
sizes[STRT] = ENDIAN_SWAP_32(current_record->size);
} else if (!memcmp(current_record->name, "Line", 4)) {
offsets[LINT] = current_pos;
sizes[LINT] = ENDIAN_SWAP_32(current_record->size);
}

current_pos += iff_align(ENDIAN_SWAP_32(current_record->size) + 8);
Expand Down
6 changes: 4 additions & 2 deletions src/libAtomVM/iff.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,13 @@ extern "C" {
#define FUNT 7
/** Str table section */
#define STRT 8
/** Str table section */
#define LINT 9

/** Required size for offsets array */
#define MAX_OFFS 9
#define MAX_OFFS 10
/** Required size for sizes array */
#define MAX_SIZES 9
#define MAX_SIZES 10

/** sizeof IFF section header in bytes */
#define IFF_SECTION_HEADER_SIZE 8
Expand Down
Loading