Code Monkey home page Code Monkey logo

niffy's Introduction

niffy - NIF testing harness

... an ad hoc, informally-specified, bug-ridden, slow implementation of 1% of Erlang

This is a very simple harness to allow running NIFs under valgrind and other debugging tools without all the machinery of building a special version of the runtime.

It allows you to load a NIF and invoke its functions with arbitrary Erlang terms (or, at least, some subset of acceptable Erlang terms).

Very little of the ERTS is implemented, so it's likely that your NIF will break in other ways if it's dependent on much of Erlang.

Etymology

From WordNet (r) 3.0 (2006) [wn]:

niffy adj 1: (British informal) malodorous

nifty was already taken.

Caveats

  • allocates lots of memory and doesn't free it
  • isn't character encoding aware (no UTF-8 support)
  • much of the NIF API is still unimplemented

License

GPLv3

Building

Requires ragel, and the header files you'd use to compile a NIF. Run make all to build everything.

Usage

Simple

You have a compiled NIF shared object nif.so and want to call some of its functions under valgrind:

$ valgrind niffy nif.so <<EOF
V = nif:foo(42).
V.
nif:bar("abc").
nif:baz(V).
EOF

(If your NIF fails to load because of missing symbols, try again with the --lazy option. You may still be able to do some things if you avoid the parts of the NIF API that aren't implemented.)

niffy expects a series of period-separated function invocations of the form Module:Function(Arguments). where Module is probably a NIF you loaded, Function is some function it defines, and the Arguments are constant Erlang terms of an appropriate arity.

You can assign the return value of a call to a variable (pattern matching is not supported), and use that variable in subsequent calls. If no variable is supplied, niffy will print the return value of each call on stdout. A variable alone will print its bound value.

It operates on a line at a time so you can interact with it to some extent, but keep in mind that function invocations are terminated by a period.

Note that the NIF's load function will not be called unless you explicitly call niffy:load_nif(nif_name, []). (You don't need to call this if your NIF doesn't have a load callback.)

Erlang BIFs available in niffy:

  • niffy:load_nif/2
  • niffy:halt/0
  • niffy:byte_size/1
  • niffy:element/2

Multiple NIFs and other libraries

You can specify several SOs on the command-line, all of which will be loaded. Not all of them have to be NIFs.

Fuzzing a NIF with afl_fuzz

Build your NIF with afl-gcc:

$ cd my_nif
$ CC=afl-gcc CXX=afl-g++ rebar co

Build niffy and fuzz_skeleton with afl-gcc:

$ cd niffy
$ make clean
$ CC=afl-gcc make

You'll need a binary to run afl-fuzz on; you could supply niffy itself, but you'd primarily be fuzzing niffy's term parser instead of your NIF. To make it easier to focus the fuzzing, there is a program provided called fuzz_skeleton.

As is, this program reads stdin as bytes of a binary which it binds to the variable Input, and then reads from a term file specified on the command line. If you're primarily interested in fuzzing your NIF with binaries, this may be all you need, and you'd supply a term file like the following:

_ = niffy:load_nif(jiffy, []).
Term = jiffy:nif_decode_init(Input, []).
jiffy:nif_encode_init(Term, []).

and then run afl-fuzz like this:

$ afl-fuzz -i input_samples -o output -- ./fuzz_skeleton ../jiffy/priv/jiffy.so jiffy_template.term

where input_samples is a directory containing some small initial test cases, and output is a directory that will be created to hold the results.

However, you probably want more control over the representation fed to your NIF, in which case you can modify fuzz_skeleton.c to accept input as appropriate for your NIF.

Tip: To make sure everything is working, inject an intentional, obvious bug into the NIF and verify that afl-fuzz catches it. Usually if you do something like make an allocation size off-by-one or similar, afl-fuzz will find a crashing case almost immediately. Then you know your setup is good, you can remove the bug, and proceed.

For extremely rudimentary property testing, the assert:eq/2 and assert:ne/2 functions are provided, which abort on assertion failure.

For example, I used the following fuzz term for testing LZ4's round-trip:

_ = niffy:load_nif(lz4, []).
Compressed = lz4:compress(Input, []).
Decompressed = lz4:decompress(Compressed, []).
assert:eq(Input, Decompressed).

Warning: If your NIF does not load without the --lazy option to niffy, you must set LD_BIND_LAZY=1 in your environment; otherwise, afl-fuzz sets LD_BIND_NOW and your fuzzer will mysteriously abort on all your test cases if the NIF uses any unimplemented functionality.

Tracing eunit and running your NIF calls through niffy

You can setup a tracing process that feeds all the calls to your NIF through niffy running under valgrind through a port, and then invoke your eunit test suite. For the simplest NIFs, something like this is sufficient:

start() ->
    Port = open_port({spawn_executable, "/usr/bin/valgrind"},
                     [{args, ["--error-exitcode=42", "--",
                              "../niffy/niffy", "./priv/my_nif.so", "-q"]},
                      binary, stream, exit_status,
                      {line, 1024}]),
    Port ! {self(), {command, <<"_ = niffy:load_nif(my_nif, []).\n">>}},
    erlang:trace_pattern({my_nif, '_', '_'}, true, []),
    erlang:trace(all, true, [call]),
    loop(Port).

loop(Port) ->
    receive
        {trace, _, call, {Module, Function, Arguments}} ->
            Port ! {self(), {command, format_mfa(Module, Function, Arguments)}},
            loop(Port);
        {_Port, {exit_status, Status}} ->
            io:format("niffy exited with code ~p~n", [Status])
    end.

In more complex situations, you may need to handle certain calls (those that return a non-printable term, for example) and rewrite them to the form Handle = my_nif:creation_call(Args)., and suitably replace later arguments that containing that non-printable term with Handle. An example should soon be included here, but until then, feel free to contact me about it.

Alternatives

As an alternative to niffy, you could build a valgrind-enabled OTP, using this recipe.

Contact

niffy is maintained by Julian Squires [email protected].

niffy's People

Contributors

tokenrove avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

Forkers

fbernier evnu

niffy's Issues

Handle full bitsyntax

Right now in an expression like <<2.2:32/float>> we ignore the :32/float but obviously many programs would like to be able to check against relatively-more-expressive binaries, so we should fix that.

Implement maps

We should do a really dumb and simple map implementation; for example, JSON NIFs are increasingly using maps.

Instrument enif calls

What's happening @tokenrove,

I continue to use niffy quite regularly in tandem with KLEE and valgrind while searching for OTP bugs. While I've already found some interesting bugs, one of the big challenges is sorting through the events that lead to a particular piece of memory being corrupted or out-of-deadsync. Niffy would be all-that-much cooler with a database for storing telemetry generated on enif_* entry and exit, right now I just have an fd that gets a bunch of stuff written to it and is processed with sort and grep.

stay niffy dude.

P.S: I just has a really embarrassing moment regarding niffy on erlang-questions. Someone posted information about their new project called nifty and having been so excited to write a glowing review of NIFFY I didn't stop to realize it was completely different. My message is in the moderation q, so I'm not sure if it will be posted but if you keep up with erlang listserv you'll have some fanmail, haha.

`enif_inspect_iolist_as_binary` cannot decode a valid term

first up, i'm here to award you the "man with da plan" genius grant for this project. this long venerated prize confers substantial benefits, up to and including lots of props and worldwide love no matter where you go.

unfortunately I can't come with entirely good news, because when iolist_to_binary encounters a binary term, it borks no matter it's contents. I've tried

  1. Manually crafting a binary through enif_make_binary
  2. Aliasing enif_inspect_iolist_as_binary to enif_inspect_as_binary -- I'd no idea if this was a sensible idea and it's not.

I haven't done any significant debugging yet, but MYYYY guess is that the parsing of the term is incorrect. I'm going to take a further look at it when I get some time.

==22361== Process terminating with default action of signal 6 (SIGABRT)
==22361== at 0x506DA28: raise (in /usr/lib64/libc-2.22.so)
==22361== by 0x506F629: abort (in /usr/lib64/libc-2.22.so)
==22361== by 0x40562C: inner_iolist_to_binary (nif_stubs.c:374)
==22361== by 0x405760: iolist_to_binary (nif_stubs.c:404)
==22361== by 0x4066BB: enif_inspect_iolist_as_binary (nif_stubs.c:815)
==22361== by 0x57FCA22: decode_ber_tlv_raw (asn1_erl_nif.c:1253)
==22361== by 0x4038A6: call (niffy.c:84)
==22361== by 0x404000: niffy_handle_statement (niffy.c:147)
==22361== by 0x412D01: yy_reduce (parse.y:56)
==22361== by 0x412D01: Parse (parse.c:1293)
==22361== by 0x40320B: main (main.c:100)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.