[erlang-questions] Issue with using enif_binary_to_term

Ross Schlaikjer rossschlaikjer@REDACTED
Tue May 15 17:01:23 CEST 2018


Vincent,

I'm not entirely sure yet why this is happening, but I think I have at
least figured out at least some of what is going on.

Heap allocation in erts is done very simply (for the most part), in that it
is linearly allocated like so (as of otp git tag OTP-19.2.1):

 140 static ERTS_INLINE Eterm* alloc_heap(ErlNifEnv* env, size_t need)
 141 {
 142     Eterm* hp = env->hp;
 143     env->hp += need;
 144     if (env->hp <= env->hp_end) {
 145     return hp;
 146     }
 147     return alloc_heap_heavy(env, need, hp);
 148 }

When allocating a new cons cell, we take two 64bit words, store car/cdr and
return the address of the base pointer + the list tag (0x1).

1726 ERL_NIF_TERM enif_make_list_cell(ErlNifEnv* env, Eterm car, Eterm cdr)
1727 {
1728     Eterm* hp = alloc_heap(env,2);
1729     Eterm ret = make_list(hp);
1730
1731     ASSERT_IN_ENV(env, car, 0, "head of list cell");
1732     ASSERT_IN_ENV(env, cdr, 0, "tail of list cell");
1733     CAR(hp) = car;
1734     CDR(hp) = cdr;
1735     return ret;
1736 }

make_list above epands to the macro
#define _unchecked_make_list(x) ((Uint)(x) + TAG_PRIMARY_LIST)
so all it's doing is adding 1 to that ret pointer.

If we take this all to gdb, we can step through your code and see the list
modifications occurring (comments inline):

18     ERL_NIF_TERM list = enif_make_list(env, 0);
(gdb) n
19     ERL_NIF_TERM in_term = enif_make_uint(env, 42);
(gdb) p/x list
$1 = 0xfffffffffffffffb  # This is the 'empty list' value
(gdb) n
22     int success = enif_term_to_binary(env, in_term, &bin);
(gdb) n
24     int bcount = enif_binary_to_term(env, bin.data, 3, &out_term1, 0);
(gdb) p/x env->hp  # Check the heap pointer before we call
enif_binary_to_term
$4 = 0x7fff79840fe0
(gdb) n
25     list = enif_make_list_cell(env, out_term1, list);
(gdb) p/x env->hp  # Check again after - notice the hp hasn't changed
$5 = 0x7fff79840fe0
(gdb) n  # Make the cons cell
27     bcount = enif_binary_to_term(env, bin.data, 3, &out_term2, 0);
(gdb) p/x list # As we can see, it has the address of the base pointer +
the list tag (1)
$6 = 0x7fff79840fe1
(gdb) x/2g list-1  # If we examine it, we see it has the head (our
immediate int value) and the tail (empty list)
0x7fff79840fe0: 0x00000000000002af 0xfffffffffffffffb
(gdb) p/x env->hp  # Our heap pointer has gone up by 16 bytes, as expected
(2 64bit words)
$7 = 0x7fff79840ff0
(gdb) n  # Now we execute the second enif_binary_to_term call
28     list = enif_make_list_cell(env, out_term2, list);
(gdb) p/x env->hp
$8 = 0x7fff79840fe0  # This is where our problem begins! binary_to_term has
_decremented_ the heap pointer!
(gdb) n  # Create our second cons cell
30     bcount = enif_binary_to_term(env, bin.data, 3, &out_term3, 0);
(gdb) p/x list  # We look at the 'new' list pointer value... but it's the
same as before!
$9 = 0x7fff79840fe1
(gdb) x/2g list -1
0x7fff79840fe0: 0x00000000000002af 0x00007fff79840fe1  # This list now
references itself!

It looks like when we call binary_to_term, the cache_env call is what's
messing with our heap pointer -
(gdb) watch env->hp
Hardware watchpoint 2: env->hp
(gdb) n

Thread 14 "1_scheduler" hit Hardware watchpoint 2: env->hp

Old value = (Eterm *) 0x7fff79840ff0
New value = (Eterm *) 0x7fff79840fe0
0x00005555556ce798 in cache_env (env=0x7fffb34bee40) at beam/erl_nif.c:421
warning: Source file is more recent than executable.
421 env->hp = HEAP_TOP(env->proc);
(gdb) bt
#0  0x00005555556ce798 in cache_env (env=0x7fffb34bee40) at
beam/erl_nif.c:421
#1  enif_binary_to_term (dst_env=0x7fffb34bee40, data=0x7fffb5b82b68
"\203a*", data_sz=<optimized out>, term=0x7fffb34bed28,
    opts=(unknown: 0)) at beam/erl_nif.c:1161


So as far as I can tell, something to do with the switchover between HAlloc
and alloc_heap (flush_env / cache_env) seems to be losing some bytes off
the heap pointer, but _only_ for subsequent calls. Unfortunately I must go
back to my day job for now, but will try and debug more later.

Hope this helps.

- Ross S

On Mon, May 14, 2018 at 4:57 PM, Vincent Siliakus <zambal@REDACTED> wrote:

> Hi all,
>
> I'm writing a NIF library and can't wrap my head around why the following
> code makes the erlang runtime hang when called from a shell:
>
>   static ERL_NIF_TERM test(ErlNifEnv* env, int argc, const ERL_NIF_TERM
> argv[]) {
>     ErlNifBinary bin;
>
>     ERL_NIF_TERM list = enif_make_list(env, 0);
>     ERL_NIF_TERM in_term = enif_make_uint(env, 42);
>     ERL_NIF_TERM out_term1, out_term2, out_term3;
>
>     enif_term_to_binary(env, in_term, &bin);
>
>     enif_binary_to_term(env, bin.data, bin.size, &out_term1, 0);
>     list = enif_make_list_cell(env, out_term1, list);
>
>     enif_binary_to_term(env, bin.data, bin.size, &out_term2, 0);
>     list = enif_make_list_cell(env, out_term2, list);
>
>     enif_binary_to_term(env, bin.data, bin.size, &out_term3, 0);
>     list = enif_make_list_cell(env, out_term3, list);
>
>     return list;
>   }
>
> The multiple calls to enif_binary_to_term somehow seem to corrupt memory
> in the calling environment, so I'm probably using it incorrectly. Could
> some kind soul point me to the error? I'm running this code on OTP20 /
> erts-9.2.
>
> Thanks in advance,
> Vincent
>
> _______________________________________________
> erlang-questions mailing list
> erlang-questions@REDACTED
> http://erlang.org/mailman/listinfo/erlang-questions
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://erlang.org/pipermail/erlang-questions/attachments/20180515/9123295b/attachment.htm>


More information about the erlang-questions mailing list