Authors: Petr Ročkai, Jiří Barnat
Abstract:

In this paper, we introduce an interactive simulator for programs in the form of LLVM bitcode. The main features of the simulator include precise control over thread scheduling, automatic checkpoints and reverse stepping, support for source-level information about functions and variables in C and C++ programs and structured heap visualisation. Additionally, DivSIM is compatible with DiVM (DIVINE VM) hypercalls, which makes it possible to load, simulate and analyse counterexamples from an existing model checker, and with abstract bitcode generated by LART (LLVM Abstraction and Refinement Tool), making it suitable for direct analysis of abstract and/or symbolic programs and counterexamples.

Documents:

Source Code

The source code which has been used for the evaluation in the paper is available here: divine-4.4.2+divsim.tar.gz. This tarball also includes the 3rd-party Qt-based graphical user interface. Unfortunately, the code does not work correctly with current version of the Qt toolkit (fix pending).

The testcases used in Section 8 (Evaluation) of the paper are under the test directory, each category (described in the paper) corresponding to a single subdirectory in the tarball. Testcases tagged todo (in the comments near the top of each file) and those in directories cc, dioscc, draw, lart, leak, liveness, misc, pdclib, sim and verify have been excluded, mainly due to their limited relevance to the simulator.

Build Instructions

To build from the source, you will need some prerequisites, namely a C++11 compiler such as reasonably recent GCC (>= 4.9) or Clang (>= 3.2), make, CMake (>= 3.2), libedit, perl, and about 12GB of disk space and 4GB RAM.

You should extract the archive and then run make divine in its main directory. This should build DIVINE into the _build.release/tools subdirectory by default. For more details, please refer to the manual.

Running the Simulator

The binary can be executed directly from the build directory and does not need to be installed. The simulator is available as divine sim. See divine help sim for detailed help on command line options. The most common invocations are:

Suitable test programs can be found, for instance, in the directory test/demo.

Bundled GUI

TODO: Add instructions to build and run the bundled version of the GUI once the code is fixed to work with Qt 5.15+.

GUI (Upstream Version)

The original version, as published by Vojtěch Frnoch, the author of the user interface, is available as well: divsim-gui.tar.gz. The following instructions were provided by the author.

Requirements:

Instructions:

  1. Extract the tarball and enter the newly created directory divine.
  2. Open a terminal there.
  3. Run make.
  4. Enter the directory divine/gui.
  5. Run cmake CMakeLists.txt.
  6. Run make.
  7. Change into ../../_build.release.gui.
  8. Run the program as ./gui.

Example Transcript

Below is a 1:1 reproduction of an example session (using one of the test programs bundled with the source code). The command prompt of divine sim is marked with >. The configuration file ~/.divine/sim.init in this case contains setup --sticky source which causes the source code of the currently executing function to be shown after each command.

Hitting enter at the prompt without typing a command repeats the last command (i.e. step in the example below).

$ divine check test/demo/undef.c
compiling test/demo/undef.c
loading bitcode … DiOS … LART … RR … constants … done
booting … done
states per second: 483.871
state count: 15
mips: 2.3

error found: yes
error trace: |
  [0] entering main
  [0] going to create thread
  [1] thread is running
  FAULT:  conditional jump depends on an undefined value
  [1] FATAL: control error in userspace

active stack:
  - symbol: void __dios::FaultBase::handler<__dios::Context>(_VM_Fault, _VM_Frame*, void (*)())
    location: /dios/sys/fault.hpp:119
  - symbol: thread
    location: test/demo/undef.c:7
  - symbol: __pthread_start
    location: /dios/libc/pthread/pthread-init.cpp:79
  - symbol: __pthread_entry
    location: /dios/libc/pthread/pthread-core.cpp:50
a report was written to undef.report

$ divine sim --load-report undef.report
compiling test/demo/undef.c

           ^       —————.   —.—  .     .  —.—  .     .  .—————    .    .
          ———      |     |   |   |     |   |   |\    |  |         |    |
         —(o)—     |     |   |   |     |   |   |  \  |  |————     '————|
        ———————    |     |   |    \   /    |   |    \|  |              |
       —————————   —————'   —'—     '     —'—  '     '  '—————         '

Welcome to 'divine sim', an interactive debugger. Type 'help' to get started.
T: [0] entering main
T: [0] going to create thread
▶ state    #5 [new] -- active threads: 0:0 [0:1] --
T: [1] thread is running
T: FAULT:  conditional jump depends on an undefined value
      4 void *thread( void *state ) {
      5     puts( "thread is running" );
      6     int *x = state;
>>    7     if ( *x == 0 ) /* ERROR */
      8         puts( "x is zero" );
      9     else if ( *x == 1 )
     10         puts( "x is one" );
     11     else
     12         puts( "x is neither zero nor one" );
     13     puts( "thread is done" );
     14     return NULL;
     15 }
# executing void __dios::FaultBase::handler<{Context}>(_VM_Fault, _VM_Frame*, void (*)())
#        at /dios/sys/fault.hpp:119
# NOTE: $frame in thread
> start
▶ state    #1 [cex] -- active threads: [0:0] --
     17 int main() {
>>   18     puts( "entering main" );
     19     pthread_t tid;
     20     int x;
     21     puts( "going to create thread" );
     22     pthread_create( &tid, NULL, thread, &x );
     23     puts( "thread created" );
     24     x = 1;
     25     puts( "initialized x, going to wait for the thread" );
     26     pthread_join( tid, NULL );
     27     puts( "thread finished, exiting" );
     28 }
# executing main at test/demo/undef.c:17
> step
  %01 = alloca [i32 1 d]                                                        # [alloca* 87582d7 0 ddp]
  %02 = alloca [i32 1 d]                                                        # [alloca* 96d518e 0 ddp]
     17 int main() {
>>   18     puts( "entering main" );
     19     pthread_t tid;
     20     int x;
     21     puts( "going to create thread" );
     22     pthread_create( &tid, NULL, thread, &x );
     23     puts( "thread created" );
     24     x = 1;
     25     puts( "initialized x, going to wait for the thread" );
     26     pthread_join( tid, NULL );
     27     puts( "thread finished, exiting" );
     28 }
# executing main at test/demo/undef.c:18
>
  %03 = call @puts [global* 6 0 ddp]                                            # [i32 0 u]
T: [0] entering main
     17 int main() {
     18     puts( "entering main" );
     19     pthread_t tid;
     20     int x;
>>   21     puts( "going to create thread" );
     22     pthread_create( &tid, NULL, thread, &x );
     23     puts( "thread created" );
     24     x = 1;
     25     puts( "initialized x, going to wait for the thread" );
     26     pthread_join( tid, NULL );
     27     puts( "thread finished, exiting" );
     28 }
# executing main at test/demo/undef.c:21
>
  %06 = call @puts [global* 7 0 ddp]                                            # [i32 0 u]
T: [0] going to create thread
     17 int main() {
     18     puts( "entering main" );
     19     pthread_t tid;
     20     int x;
     21     puts( "going to create thread" );
>>   22     pthread_create( &tid, NULL, thread, &x );
     23     puts( "thread created" );
     24     x = 1;
     25     puts( "initialized x, going to wait for the thread" );
     26     pthread_join( tid, NULL );
     27     puts( "thread finished, exiting" );
     28 }
# executing main at test/demo/undef.c:22
>
  %08 = call @pthread_create %01 [global* 0 0 ddn] thread %07                   # [i32 0 u]
▶ state    #2 [cex] -- active threads: 0:0 [0:1] --
▶ state    #3 [cex] -- active threads: 0:0 [0:1] --
      4 void *thread( void *state ) {
>>    5     puts( "thread is running" );
      6     int *x = state;
      7     if ( *x == 0 ) /* ERROR */
      8         puts( "x is zero" );
      9     else if ( *x == 1 )
     10         puts( "x is one" );
     11     else
     12         puts( "x is neither zero nor one" );
     13     puts( "thread is done" );
     14     return NULL;
     15 }
# executing thread at test/demo/undef.c:5
> step
  %07 = call @puts [global* 1 0 ddp]                                            # [i32 0 u]
▶ state    #4 [cex] -- active threads: [0:0] 0:1 --
     17 int main() {
     18     puts( "entering main" );
     19     pthread_t tid;
     20     int x;
     21     puts( "going to create thread" );
     22     pthread_create( &tid, NULL, thread, &x );
>>   23     puts( "thread created" );
     24     x = 1;
     25     puts( "initialized x, going to wait for the thread" );
     26     pthread_join( tid, NULL );
     27     puts( "thread finished, exiting" );
     28 }
# executing main at test/demo/undef.c:23
> step
  %09 = call @puts [global* 8 0 ddp]                                            # [i32 0 u]
▶ state    #5 [cex] -- active threads: 0:0 [0:1] --
T: [1] thread is running
      4 void *thread( void *state ) {
      5     puts( "thread is running" );
      6     int *x = state;
>>    7     if ( *x == 0 ) /* ERROR */
      8         puts( "x is zero" );
      9     else if ( *x == 1 )
     10         puts( "x is one" );
     11     else
     12         puts( "x is neither zero nor one" );
     13     puts( "thread is done" );
     14     return NULL;
     15 }
# executing thread at test/demo/undef.c:7