atomvm / AtomVM

Tiny Erlang VM
https://www.atomvm.net
Apache License 2.0
1.49k stars 106 forks source link

Interesting crash on spawn when passing structure with nested list #54

Closed fadushin closed 5 years ago

fadushin commented 5 years ago

I can get AtomVM to crash in some edge cases by passing a list in a nested structure to spawn.

Here is a sample program that crashes AtomVM:

-module(test_spawn_list).

-export([start/0, loop/1]).

start() ->
    Pid = spawn(?MODULE, loop, [{foo, {bar, [haddocks, eyes]}}]),
    Pid ! stop,
    0.

loop(List) ->
    receive
        stop -> ok;
        _ -> loop(List)
    end.

The output is:

Seed is 1547674607
-- EXECUTING TEST: test_spawn_list.beam
- Found unknown boxed type: f
Abort trap: 6

The stack trace is:

#3  0x000000010001cfe7 in memory_scan_and_copy at /work/src/github/fadushin/AtomVM/src/libAtomVM/memory.c:336
#4  0x000000010001c9e7 in memory_gc at /work/src/github/fadushin/AtomVM/src/libAtomVM/memory.c:115
#5  0x000000010001c529 in mailbox_peek at /work/src/github/fadushin/AtomVM/src/libAtomVM/mailbox.c:98
#6  0x0000000100008a77 in context_execute_loop at /work/src/github/fadushin/AtomVM/src/libAtomVM/opcodesswitch.h:1219
#7  0x0000000100001355 in test_modules_execution at /work/src/github/fadushin/AtomVM/tests/test.c:256
#8  0x0000000100001514 in main at /work/src/github/fadushin/AtomVM/tests/test.c:291

We hit the abort statement in this part of the code in memory_scan_and_copy:

        } else if ((t & 0x3) == 0x0) {
            TRACE("Found boxed header (%lx)\n", t);

            switch (t & TERM_BOXED_TAG_MASK) {
                case TERM_BOXED_TUPLE: {
                    int arity = term_get_size_from_boxed_header(t);
                    TRACE("- Boxed is tuple (%lx), arity: %i\n", t, arity);

                    for (int i = 1; i <= arity; i++) {
                        TRACE("-- Elem: %lx\n", ptr[i]);
                        ptr[i] = memory_shallow_copy_term(ptr[i], &new_heap, move);
                    }
                    break;
                }

                case TERM_BOXED_REF:
                    TRACE("- Found ref.\n");
                    break;

                case TERM_BOXED_HEAP_BINARY:
                    TRACE("- Found binary.\n");
                    break;

                default:
                    fprintf(stderr, "- Found unknown boxed type: %lx\n", (t >> 2) & 0xF);
                    abort(); <-- here
            }

            ptr += term_get_size_from_boxed_header(t) + 1;

        } else if (term_is_nonempty_list(t)) {
fadushin commented 5 years ago

Just a tiny bit more information on this, because I can reproduce a similar issue in my gen_server test. With the test_spawn_list.beam, I get the following SEGV in memory_shallow_copy_term on Linux (Ubuntu):

20:04:20 castro:/work/src/github/fadushin/AtomVM/build> gdb src/AtomVM core
GNU gdb (Ubuntu 8.1-0ubuntu3) 8.1.0.20180409-git
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from src/AtomVM...done.
[New LWP 90202]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Core was generated by `./src/AtomVM test_spawn_list.beam'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0  0x000055ba679f62ca in memory_is_moved_marker (t=0x30) at /work/src/github/fadushin/AtomVM/src/libAtomVM/memory.c:136
136     return *t == 0x2B;
(gdb) where
#0  0x000055ba679f62ca in memory_is_moved_marker (t=0x30) at /work/src/github/fadushin/AtomVM/src/libAtomVM/memory.c:136
#1  0x000055ba679f6cc7 in memory_shallow_copy_term (t=49, new_heap=0x7fff3a73ff90, move=1) at /work/src/github/fadushin/AtomVM/src/libAtomVM/memory.c:407
#2  0x000055ba679f6a5e in memory_scan_and_copy (mem_start=0x55ba68374e20, mem_end=0x55ba68374e30, new_heap_pos=0x7fff3a740008, move=1)
    at /work/src/github/fadushin/AtomVM/src/libAtomVM/memory.c:343
#3  0x000055ba679f621e in memory_gc (ctx=0x55ba68374c20, new_size=8) at /work/src/github/fadushin/AtomVM/src/libAtomVM/memory.c:115
#4  0x000055ba679f5ee7 in memory_ensure_free (c=0x55ba68374c20, size=2) at /work/src/github/fadushin/AtomVM/src/libAtomVM/memory.c:54
#5  0x000055ba679e3e52 in context_execute_loop (ctx=0x55ba68374c20, mod=0x55ba68374470, function_name=0x55ba67a0ae08 "start", arity=0)
    at /work/src/github/fadushin/AtomVM/src/libAtomVM/opcodesswitch.h:971
#6  0x000055ba679dfba0 in main (argc=2, argv=0x7fff3a740948) at /work/src/github/fadushin/AtomVM/src/main.c:84
(gdb) list
131 
132 
133 static inline int memory_is_moved_marker(term *t)
134 {
135     // 0x2B is an unused tag
136     return *t == 0x2B;
137 }
138 
139 static inline void memory_replace_with_moved_marker(term *to_be_replaced, term replace_with)
140 {

On OS X I get:

(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x109e4b000)
  * frame #0: 0x000000010001da4d AtomVM`memory_shallow_copy_term(t=4460867489, new_heap=0x00007ffeefbfdb70, move=0) at memory.c:413
    frame #1: 0x000000010001dd0c AtomVM`memory_scan_and_copy(mem_start=0x0000000109e4aff0, mem_end=0x0000000109e4b000, new_heap_pos=0x00007ffeefbfdbb0, move=0) at memory.c:343
    frame #2: 0x000000010001de14 AtomVM`memory_copy_term_tree(new_heap=0x0000000109e48f90, t=4460867426) at memory.c:160
    frame #3: 0x000000010002db45 AtomVM`nif_erlang_spawn_3(ctx=0x0000000109a83ed0, argc=3, argv=0x0000000109a83ef8) at nifs.c:462
    frame #4: 0x0000000100005727 AtomVM`context_execute_loop(ctx=0x0000000109a83ed0, mod=0x0000000109a3cf90, function_name="start", arity=0) at opcodesswitch.h:760
    frame #5: 0x000000010000148e AtomVM`main(argc=2, argv=0x00007ffeefbff480) at main.c:86
    frame #6: 0x00007fff73515ed9 libdyld.dylib`start + 1

when passing the term {state,undefined,test_gen_server,{state,0,0}} to the proc.

The loop in:

        int boxed_size = term_boxed_size(t) + 1;
        term *dest = *new_heap;
        for (int i = 0; i < boxed_size; i++) {
            dest[i] = boxed_value[i];
        }

crashes on the last iteration, when the term is {state, 0, 0} -- boxed_size in this case is 4, which I assume is 1 for the tuple, and 3 for each of the three elements.

fadushin commented 5 years ago

I can get my spawn programs to not crash by ensuring we have enough space in the heap before a spawn.

10:54:54 spock:/work/src/github/fadushin/AtomVM> git diff src/libAtomVM/nifs.c
diff --git a/src/libAtomVM/nifs.c b/src/libAtomVM/nifs.c
index aa6b2a3..d239c59 100644
--- a/src/libAtomVM/nifs.c
+++ b/src/libAtomVM/nifs.c
@@ -457,6 +457,7 @@ static term nif_erlang_spawn_3(Context *ctx, int argc, term argv[])
     //TODO: check available registers count
     int reg_index = 0;
     term t = argv[2];
+    memory_ensure_free(new_ctx, memory_estimate_usage(t));
     while (!term_is_nil(t)) {
         term *t_ptr = term_get_list_ptr(t);
         new_ctx->x[reg_index] = memory_copy_term_tree(&new_ctx->heap_ptr, t_ptr[1]);
fadushin commented 5 years ago

Added change to https://github.com/bettio/AtomVM/pull/53

fadushin commented 5 years ago

Closed as PR 53 has been merged