Wednesday, November 25, 2009

Using NIF for E V I L

A couple of weeks ago at work we had a problem with memory growth in the erlang VM. We could see binaries that were being kept around using process_info(Pid, binary), but the only way we could work out what was in the binary was by attaching gdb and walking the structures.

Using gdb to walk memory structures inside the VM is a pain. Along comes NIF with the R13B03 release which gives me a good excuse to have a go with that instead. After a quick look at the tutorial by Paul Joseph Davis I present sNIFfer - a very naughty peek at the VM internals with no regard to locking or thread safety.

Using the module you can go from an address listed by process_info back to (a copy of) the binary again.


Erlang R13B03 (erts-5.7.4) [source] [64-bit] [smp:2:2] [rq:2] [async-threads:0] [kernel-poll:false]

Eshell V5.7.4 (abort with ^G)
1> sniffer:start().
ok
2> B= <<"abc">>.
<<"abc">>
3> process_info(self(), binary).
{binary,[{4303709336,3,3}]}
4> sniffer:get_binary(4303709336).
<<"abc">>


Here is the C code


// sniffer.c
#include
#include
#include "erl_nif.h"

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include "sys.h"
#include "erl_vm.h"
#include "global.h"

static int
load(ErlNifEnv* env, void** priv, ERL_NIF_TERM load_info)
{
return 0;
}

static int
reload(ErlNifEnv* env, void** priv, ERL_NIF_TERM load_info)
{
return 0;
}

static int
upgrade(ErlNifEnv* env, void** priv, void** old_priv,
ERL_NIF_TERM load_info)
{
return 0;
}

static void
unload(ErlNifEnv* env, void* priv)
{
return;
}

static ERL_NIF_TERM
get_binary(ErlNifEnv* env, ERL_NIF_TERM a1)
{
unsigned long mem_loc;
if (!enif_get_ulong(env, a1, &mem_loc))
{
return enif_make_badarg(env);
}
else
{
Binary* bin_ptr = (Binary*) mem_loc;
ErlNifBinary nif_bin;

enif_alloc_binary(env, bin_ptr->orig_size, &nif_bin);
memcpy(nif_bin.data, bin_ptr->orig_bytes, bin_ptr->orig_size);
return enif_make_binary(env, &nif_bin);
}
}

static ErlNifFunc sniffer_funcs[] =
{
{"get_binary", 1, get_binary}
};

ERL_NIF_INIT(sniffer, sniffer_funcs, load, reload, upgrade, unload)


The erlang module


%% sniffer.erl
-module(sniffer).
-export([start/0, get_binary/1]).

start() ->
erlang:load_nif("sniffer", 0).

get_binary(_Val) ->
nif_error(?LINE).

nif_error(Line) ->
exit({nif_not_loaded,module,?MODULE,line,Line}).



And a makefile - you'll need to update ERL_TOP to point to your VM source tree.


# Makefile
ERL_TOP=/Users/jmeredith/git/erlang0d
include $(ERL_TOP)/make/target.mk

INCLUDES = \
-I$(ERL_TOP)/erts/$(TARGET) \
-I$(ERL_TOP)/erts/emulator/$(TARGET) \
-I$(ERL_TOP)/erts/emulator/$(TARGET)/opt/smp \
-I$(ERL_TOP)/erts/emulator/beam/ \
-I$(ERL_TOP)/erts/emulator/sys/unix \
-I$(ERL_TOP)/erts/include/$(TARGET) \
-I$(ERL_TOP)/erts/include/internal \
-no-cpp-precomp -DHAVE_CONFIG_H

# OS X Snow Leopard flags.
GCCFLAGS = -m64 -O3 -fPIC -bundle -flat_namespace -undefined suppress -fno-common -Wall

# Linux Flags
#GCCFLAGS = -O3 -fPIC -shared -fno-common -Wall

CFLAGS = $(GCCFLAGS) $(INCLUDES)
LDFLAGS = $(GCCFLAGS) $(LIBS)

OBJECTS = sniffer.o

DRIVER = sniffer.so
BEAM = sniffer.beam

all: $(DRIVER) $(BEAM)

clean:
rm -f *.o *.beam $(DRIVER)

$(DRIVER): $(OBJECTS)
gcc -o $@ $^ $(LDFLAGS)

$(BEAM): sniffer.erl
erlc $^

No comments: