build(tools/cbmc): integrate cbmc-viewer
Add option to the build system to call cbmc-viewer. The following
changes were necessary:
- Add option to make call of cbmc-viewer optional
- Update CBMC Application note with cbmc-viewer
- Separate CBMC run to 2 steps: compiling to GOTO program, and call
CBMC on the resulting goto program. This is necessary becasue
cbmc-viewer operates on the GOTO program.
- Rewrite extraction of summary, as XML output (required by CBMC) files
doesn't contain summary
Change-Id: Ieb2623d3b03845dcb4ab74baead81d3c0fc6dee8
Signed-off-by: Mate Toth-Pal <mate.toth-pal@arm.com>
diff --git a/docs/getting_started/build-options.rst b/docs/getting_started/build-options.rst
index 6dab78d..de0e8cc 100644
--- a/docs/getting_started/build-options.rst
+++ b/docs/getting_started/build-options.rst
@@ -281,6 +281,7 @@
HOST_MEM_SIZE , ,0x40000000 ,"Host memory size that will be used as physical granules"
RMM_COVERAGE ,ON | OFF ,OFF ,"Enable coverage analysis"
RMM_HTML_COV_REPORT ,ON | OFF ,ON ,"Enable HTML output report for coverage analysis"
+ RMM_CBMC_VIEWER_OUTPUT ,ON | OFF ,OFF ,"Generate report of CBMC results using the tool cbmc-viewer"
.. _llvm_build:
diff --git a/docs/resources/application-notes/cbmc.rst b/docs/resources/application-notes/cbmc.rst
index 01378f6..0564ef3 100644
--- a/docs/resources/application-notes/cbmc.rst
+++ b/docs/resources/application-notes/cbmc.rst
@@ -57,7 +57,7 @@
with their respective build targets.
CBMC Assert
-===========
+-----------
In this mode CBMC is configured, so that it tries to find inputs that makes an
assertion in the code to fail. If there is such an input, then CBMC provides a
@@ -66,7 +66,7 @@
To use this mode the target ``cbmc-assert`` must be passed to the build command.
CBMC Analysis
-=============
+-------------
In this mode CBMC is configured to generate assertions for certain properties in
the code. The properties are selected so that for example no buffer overflows,
@@ -79,7 +79,7 @@
command.
CBMC Coverage
-=============
+-------------
This mode checks whether all the conditions for an ABI function are covered.
The pre and post conditions for the command are expressed as boolean values in
@@ -96,10 +96,48 @@
For all the modes the summary files are committed in the source tree as
baseline in ``tools/cbmc/testbenches_results/BASELINE.${MODE}``.
+cbmc-viewer
+===========
+
+cbmc-viewer is a python package that can parse the XML output of CBMC. It
+generates a html report that can be opened in a browser. The report contains a
+collapsible representation of assert traces, and clickable links to the source
+code locations associated with a specific trace item.
+
+The RMM cmake build system is capable of generating the cbmc-viewer report. If
+the option ``-DRMM_CBMC_VIEWER_OUTPUT=ON`` is passed to the RMM Cmake
+configuration command then the Cmake system calls cbmc-viewer and generates the
+report under ``${RMM_BUILD_DIR}/tools/cbmc/cbmc_${MODE}_results/report``
+
+Please note that the CMake build system currently only generates report for the
+``cbmc-assert`` target. The ``cbmc-coverage`` and ``cbmc-analysis`` targets
+doesn't generate trace, so generating a report wouldn't be useful.
+
+``cbmc-viewer`` can be installed using the following command:
+
+.. code-block:: bash
+
+ python3 -m pip install cbmc-viewer
+
+For further details and installation guide on cbmc-viewer please see the
+`cbmc-viewer github page`_.
+
+CBMC proof debugger
+===================
+
+CBMC proof debugger is an extension to a popular code editor that can be used to
+load the json summaries of a CBMC analysis that is generated by cbmc-viewer.
+The trace then can be explored in the built in debugger of the editor as if
+stepping through an actual code execution.
+
+For further details on installing and using the extension please see
+`CBMC proof debugger in the editor's extensions page`_.
+
-----
.. _CBMC Home: https://www.cprover.org/cbmc/
.. _Writing a good proof: https://model-checking.github.io/cbmc-training/management/Write-a-good-proof.html
.. _github release page: https://github.com/diffblue/cbmc/releases
.. _Automatically Generating Properties: https://www.cprover.org/cprover-manual/properties/
-
+.. _cbmc-viewer github page: https://github.com/model-checking/cbmc-viewer
+.. _CBMC proof debugger in the editor's extensions page: https://marketplace.visualstudio.com/items?itemName=model-checking.cbmc-proof-debugger
diff --git a/tools/cbmc/CMakeLists.txt b/tools/cbmc/CMakeLists.txt
index 7cf9c57..d6218db 100644
--- a/tools/cbmc/CMakeLists.txt
+++ b/tools/cbmc/CMakeLists.txt
@@ -4,6 +4,11 @@
#
include("SourceCollectHelpers.cmake")
+arm_config_option(
+ NAME RMM_CBMC_VIEWER_OUTPUT
+ HELP "Use XML format for CBMC output and generate cbmc-viewer report."
+ DEFAULT OFF)
+
if("${RMM_CBMC_STATIC_ANALYSIS}")
if (NOT (${HOST_VARIANT} STREQUAL "host_cbmc"))
@@ -64,6 +69,7 @@
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
COMMAND ${CMAKE_COMMAND}
-DRMM_CBMC_CONFIGURATION=ANALYSIS
+ -DRMM_CBMC_VIEWER_OUTPUT=${RMM_CBMC_VIEWER_OUTPUT}
-DSOURCE_DIR=${CMAKE_SOURCE_DIR}
-DBINARY_DIR=${CMAKE_CURRENT_BINARY_DIR}
-DTESTBENCH_DIR="${CMAKE_CURRENT_SOURCE_DIR}/testbenches"
@@ -76,6 +82,7 @@
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
COMMAND ${CMAKE_COMMAND}
-DRMM_CBMC_CONFIGURATION=ASSERT
+ -DRMM_CBMC_VIEWER_OUTPUT=${RMM_CBMC_VIEWER_OUTPUT}
-DSOURCE_DIR=${CMAKE_SOURCE_DIR}
-DBINARY_DIR=${CMAKE_CURRENT_BINARY_DIR}
-DTESTBENCH_DIR="${CMAKE_CURRENT_SOURCE_DIR}/testbenches"
diff --git a/tools/cbmc/CheckCBMC.cmake b/tools/cbmc/CheckCBMC.cmake
index 4eecfdb..0ce1a18 100644
--- a/tools/cbmc/CheckCBMC.cmake
+++ b/tools/cbmc/CheckCBMC.cmake
@@ -7,6 +7,10 @@
include("${SOURCE_DIR}/tools/cbmc/SummaryHelpers.cmake")
find_program(RMM_CBMC_PATH "cbmc"
DOC "Path to cbmc.")
+find_program(RMM_GOTO_CC_PATH "goto-cc"
+ DOC "Path to goto-cc.")
+find_program(RMM_CBMC_VIEWER_PATH "cbmc-viewer"
+ DOC "Path to cbmc-viewer.")
find_package(Python3 REQUIRED)
find_program(CHECK_CBMC_SUMMARY_EXECUTABLE "compare_summary.py"
@@ -22,22 +26,15 @@
message(FATAL_ERROR "cbmc executable not found. (${RMM_CBMC_PATH})")
endif()
-if("${RMM_CBMC_CONFIGURATION}" STREQUAL "COVERAGE")
- set(CBMC_RESULT_FILE_SUFFIX "coverage")
- set(CBMC_SUMMARY_HEADER "COVERAGE")
- set(CBMC_SUMMARY_PATTERN "covered")
-elseif("${RMM_CBMC_CONFIGURATION}" STREQUAL "ASSERT")
- set(CBMC_RESULT_FILE_SUFFIX "assert")
- set(CBMC_SUMMARY_HEADER "ASSERT")
- set(CBMC_SUMMARY_PATTERN "failed")
-elseif("${RMM_CBMC_CONFIGURATION}" STREQUAL "ANALYSIS")
- set(CBMC_RESULT_FILE_SUFFIX "analysis")
- set(CBMC_SUMMARY_HEADER "ANALYSIS")
- set(CBMC_SUMMARY_PATTERN "failed")
-else()
- message(FATAL_ERROR "Invalid RMM_CBMC_CONFIGURATION '${RMM_CBMC_CONFIGURATION}'")
-endif()
+string(TOLOWER "${RMM_CBMC_CONFIGURATION}" CBMC_RESULT_FILE_SUFFIX)
+if(RMM_CBMC_VIEWER_OUTPUT)
+ set(CBMC_OUT_FILE_ENDING "xml")
+ set(CBMC_UI_OPTION "--xml-ui")
+else()
+ set(CBMC_OUT_FILE_ENDING "output")
+ set(CBMC_UI_OPTION "")
+endif()
set(RMM_TESTBENCH_RESULT_DIR "${BINARY_DIR}/cbmc_${CBMC_RESULT_FILE_SUFFIX}_results")
set(SUMMARY_FILE "SUMMARY.${CBMC_RESULT_FILE_SUFFIX}")
@@ -111,7 +108,6 @@
"--memory-leak-check")
set(cbmc_flags_list
- "--c11"
"--timestamp;wall"
"--verbosity;9"
# Optimisation flags:
@@ -148,55 +144,131 @@
# Execute CBMC on the testbench files
#
rmm_cbmc_write_summary_header(${RMM_CBMC_SUMMARY_FIELD_WIDTH}
- ${RMM_TESTBENCH_RESULT_DIR} ${SUMMARY_FILE} ${CBMC_SUMMARY_HEADER})
+ ${RMM_TESTBENCH_RESULT_DIR} ${SUMMARY_FILE} ${RMM_CBMC_CONFIGURATION})
+
+function(rmm_cbmc_gen_file_names
+ testbench_file_path
+ filename_prefix
+ out_file_ending
+ cmd_file_var_name
+ out_file_var_name
+ err_file_var_name)
+ get_filename_component(testbench_file_name "${testbench_file_path}" NAME)
+ get_filename_component(parent "${testbench_file_path}" DIRECTORY)
+ set("${cmd_file_var_name}" "${parent}/${filename_prefix}_${testbench_file_name}.cmd" PARENT_SCOPE)
+ set("${out_file_var_name}" "${parent}/${filename_prefix}_${testbench_file_name}.${out_file_ending}" PARENT_SCOPE)
+ set("${err_file_var_name}" "${parent}/${filename_prefix}_${testbench_file_name}.error" PARENT_SCOPE)
+endfunction()
+
+function(normalise_cmd cmd_str out_var_name)
+ # replace the ; with space
+ string (REPLACE ";" " " cmd_str "${cmd_str}")
+ set("${out_var_name}" "${cmd_str}" PARENT_SCOPE)
+endfunction()
foreach(testbench_file ${TESTBENCH_FILES})
string(REPLACE ${TESTBENCH_DIR}/ "" testbench_filename ${testbench_file})
string(REGEX REPLACE "\\.[^\\.]*$" "" entry_point "${testbench_filename}")
- set(cmd
- ${RMM_CBMC_PATH}
- ${cbmc_flags_list}
- "--function;${entry_point}"
- ${RMM_IMP_INCS}
- ${cbmc_unwinds_list}
- ${cbmc_defines_list}
- ${RMM_IMP_SRCS}
- ${testbench_file})
+ set(RMM_GOTO_PROG_NAME "${RMM_TESTBENCH_RESULT_DIR}/rmm_${entry_point}.goto")
# Set the names of output files
- string(REPLACE ${TESTBENCH_DIR} ${RMM_TESTBENCH_RESULT_DIR} output_file ${testbench_file})
- set(error_file ${output_file})
- set(cmd_file ${output_file})
- string(APPEND output_file ".${CBMC_RESULT_FILE_SUFFIX}" ".output")
- string(APPEND error_file ".${CBMC_RESULT_FILE_SUFFIX}" ".error")
- string(APPEND cmd_file ".${CBMC_RESULT_FILE_SUFFIX}" ".cmd")
+ string(REPLACE ${TESTBENCH_DIR} ${RMM_TESTBENCH_RESULT_DIR} OUT_FILE_NAME_PREFIX "${testbench_file}")
+ rmm_cbmc_gen_file_names(${OUT_FILE_NAME_PREFIX} "cbmc" "${CBMC_OUT_FILE_ENDING}"
+ cbmc_cmd_file cbmc_output_file cbmc_error_file)
+ rmm_cbmc_gen_file_names(${OUT_FILE_NAME_PREFIX} "cbmc_prop" "xml"
+ cbmc_prop_cmd_file cbmc_prop_output_file cbmc_prop_error_file)
+ rmm_cbmc_gen_file_names(${OUT_FILE_NAME_PREFIX} "goto_cc" "output"
+ goto_cc_cmd_file goto_cc_output_file goto_cc_error_file)
+ rmm_cbmc_gen_file_names(${OUT_FILE_NAME_PREFIX} "cbmc_viewer" "output"
+ cbmc_viewer_cmd_file cbmc_viewer_output_file cbmc_viewer_error_file)
+ set(CBMC_VIEWER_REPORT_DIR "${RMM_TESTBENCH_RESULT_DIR}/report_${entry_point}")
- # remove the absolute path making it relative
- string (REPLACE "${SOURCE_DIR}/" "" cmd "${cmd}")
- # replace the ; with space
- string (REPLACE ";" " " CMD_STR "${cmd}")
- # add double quotes
- string (REPLACE "\"" "\\\"" CMD_STR "${CMD_STR}")
- # escape parentheses
- string (REPLACE "(" "\"(" CMD_STR "${CMD_STR}")
- string (REPLACE ")" ")\"" CMD_STR "${CMD_STR}")
- file(WRITE ${cmd_file} "${CMD_STR}")
+ set(goto_cc_cmd
+ ${RMM_GOTO_CC_PATH}
+ ${cbmc_compiler_options}
+ ${cbmc_defines_list}
+ "--function;${entry_point}"
+ "-o;${RMM_GOTO_PROG_NAME}"
+ ${RMM_IMP_INCS}
+ ${RMM_IMP_SRCS}
+ ${testbench_file}
+ )
+
+ set(cbmc_cmd
+ ${RMM_CBMC_PATH}
+ ${CBMC_UI_OPTION}
+ ${cbmc_flags_list}
+ ${cbmc_unwinds_list}
+ ${RMM_GOTO_PROG_NAME})
+
+ set(cbmc_prop_cmd
+ ${RMM_CBMC_PATH}
+ ${cbmc_flags_list}
+ "--xml-ui"
+ "--show-properties"
+ ${RMM_GOTO_PROG_NAME})
+
+ set(cbmc_viewer_cmd
+ "${RMM_CBMC_VIEWER_PATH}"
+ "--goto;${RMM_GOTO_PROG_NAME}"
+ "--result;${cbmc_output_file}"
+ "--property;${cbmc_prop_output_file}"
+ "--srcdir;${CMAKE_SOURCE_DIR}"
+ "--reportdir;${CBMC_VIEWER_REPORT_DIR}")
+
+ # remove the absolute path making it relative (shorten the command line)
+ string (REPLACE "${SOURCE_DIR}/" "" goto_cc_cmd "${goto_cc_cmd}")
+
+ normalise_cmd("${goto_cc_cmd}" GOTO_CC_CMD_STR)
+ normalise_cmd("${cbmc_cmd}" CBMC_CMD_STR)
+
+ file(WRITE ${goto_cc_cmd_file} "${GOTO_CC_CMD_STR}")
+ file(WRITE ${cbmc_cmd_file} "${CBMC_CMD_STR}")
execute_process(COMMAND ${CMAKE_COMMAND} -E echo_append "CBMC: ${testbench_file}... ")
execute_process(
- COMMAND ${cmd}
+ COMMAND ${goto_cc_cmd}
RESULT_VARIABLE res_var
- OUTPUT_FILE ${output_file}
- ERROR_FILE ${error_file}
- )
+ OUTPUT_FILE ${goto_cc_output_file}
+ ERROR_FILE ${goto_cc_error_file})
+ if (NOT ${res_var} EQUAL "0")
+ message(FATAL_ERROR "Compiling testbench with goto-cc failed. For details see: ${goto_cc_error_file}")
+ endif()
+
+ execute_process(
+ COMMAND ${cbmc_cmd}
+ RESULT_VARIABLE res_var
+ OUTPUT_FILE ${cbmc_output_file}
+ ERROR_FILE ${cbmc_error_file})
+
+ if(RMM_CBMC_VIEWER_OUTPUT)
+ normalise_cmd("${cbmc_prop_cmd}" CBMC_PROP_CMD_STR)
+ file(WRITE ${cbmc_prop_cmd_file} "${CBMC_PROP_CMD_STR}")
+ execute_process(
+ COMMAND ${cbmc_prop_cmd}
+ RESULT_VARIABLE res_var
+ OUTPUT_FILE ${cbmc_prop_output_file}
+ ERROR_FILE ${cbmc_prop_error_file})
+
+ normalise_cmd("${cbmc_viewer_cmd}" CBMC_VIEWER_CMD_STR)
+ file(WRITE ${cbmc_viewer_cmd_file} "${CBMC_VIEWER_CMD_STR}")
+ execute_process(
+ COMMAND ${cbmc_viewer_cmd}
+ RESULT_VARIABLE res_var
+ OUTPUT_FILE ${cbmc_viewer_output_file}
+ ERROR_FILE ${cbmc_viewer_error_file})
+ if (NOT ${res_var} EQUAL "0")
+ message(FATAL_ERROR "Failed to run cbmc-viewer. For details see: ${goto_cc_error_file}")
+ endif()
+ endif()
execute_process(COMMAND ${CMAKE_COMMAND} -E echo "DONE")
- rmm_cbmc_append_summary("${testbench_filename}" "${output_file}"
- ${RMM_CBMC_SUMMARY_FIELD_WIDTH} ${RMM_TESTBENCH_RESULT_DIR} ${SUMMARY_FILE}
- ${CBMC_SUMMARY_PATTERN})
+ rmm_cbmc_append_summary("${testbench_filename}" "${cbmc_output_file}"
+ "${CBMC_RESULT_FILE_SUFFIX}-${CBMC_OUT_FILE_ENDING}"
+ ${RMM_CBMC_SUMMARY_FIELD_WIDTH} ${RMM_TESTBENCH_RESULT_DIR} ${SUMMARY_FILE})
endforeach()
message(STATUS "Result in ${RMM_TESTBENCH_RESULT_DIR}")
diff --git a/tools/cbmc/SummaryHelpers.cmake b/tools/cbmc/SummaryHelpers.cmake
index ca53361..0e920f0 100644
--- a/tools/cbmc/SummaryHelpers.cmake
+++ b/tools/cbmc/SummaryHelpers.cmake
@@ -44,11 +44,40 @@
rmm_cbmc_append_separator(${summary_width} ${result_dir} ${file})
endfunction()
-function(rmm_cbmc_append_summary testbench_filename output_file summary_width result_dir file summary_pattern)
+function (rmm_cbmc_generate_summary file mode summary_var_name)
+ execute_process(COMMAND grep -cE "Solving with" ${output_file} OUTPUT_VARIABLE iteration_counts)
+ if(${iteration_counts} EQUAL "0")
+ set("${summary_var_name}" "" PARENT_SCOPE)
+ return()
+ endif()
+ if(("${mode}" STREQUAL "assert-output") OR ("${mode}" STREQUAL "analysis-output"))
+ execute_process(COMMAND grep -cE "\\[.*\\] .*: SUCCESS" ${output_file} OUTPUT_VARIABLE passed_asserts)
+ execute_process(COMMAND grep -cE "\\[.*\\] .*: FAILURE" ${output_file} OUTPUT_VARIABLE failed_asserts)
+ math(EXPR all_asserts "${passed_asserts} + ${failed_asserts}")
+ set("${summary_var_name}" "** ${failed_asserts} of ${all_asserts} failed (${iteration_counts} iterations)" PARENT_SCOPE)
+ elseif(("${mode}" STREQUAL "assert-xml") OR ("${mode}" STREQUAL "analysis-xml"))
+ execute_process(COMMAND grep -cE "property=.*status=.SUCCESS" ${output_file} OUTPUT_VARIABLE passed_asserts)
+ execute_process(COMMAND grep -cE "property=.*status=.FAILURE" ${output_file} OUTPUT_VARIABLE failed_asserts)
+ math(EXPR all_asserts "${passed_asserts} + ${failed_asserts}")
+ set("${summary_var_name}" "** ${failed_asserts} of ${all_asserts} failed (${iteration_counts} iterations)" PARENT_SCOPE)
+ elseif(("${mode}" STREQUAL "coverage-xml") OR ("${mode}" STREQUAL "coverage-output"))
+ execute_process(COMMAND grep -cE "\\[.*\\] file .* line .* function .*: SATISFIED" ${output_file} OUTPUT_VARIABLE passed_coverages)
+ execute_process(COMMAND grep -cE "\\[.*\\] file .* line .* function .*: FAILED" ${output_file} OUTPUT_VARIABLE failed_coverages)
+ math(EXPR all_coverages "${passed_coverages} + ${failed_coverages}")
+ math(EXPR cover_percent_int_part "${passed_coverages} * 100 / ${all_coverages}")
+ math(EXPR cover_percent_dec_part "(${passed_coverages} * 1000 / ${all_coverages}) % 10")
+ set("${summary_var_name}" "** ${passed_coverages} of ${all_coverages} covered (${cover_percent_int_part}.${cover_percent_dec_part}%)" PARENT_SCOPE)
+ else()
+ message(FATAL_ERROR "Unknown mode ${mode}")
+ endif()
+
+endfunction()
+
+function(rmm_cbmc_append_summary testbench_filename output_file mode summary_width result_dir file)
rmm_cbmc_align_to_middle(${summary_width} " " ${testbench_filename})
set(testbench_filename "${aligned_text}")
- execute_process(COMMAND grep -E "${summary_pattern}" ${output_file} OUTPUT_VARIABLE testbench_result)
+ rmm_cbmc_generate_summary("${output_file}" "${mode}" testbench_result)
if("${testbench_result}" STREQUAL "")
rmm_cbmc_align_to_middle(${summary_width} " " "N/A")