From cc0ad418e04aab3db4da824689e7a2688680bba2 Mon Sep 17 00:00:00 2001 From: Michael Karlesky Date: Wed, 12 Jun 2024 21:18:37 -0400 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20More=20better=20gdb=20exec?= =?UTF-8?q?ution?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Used elements of PR #856 to robustify gdb execution - Placed gdb commands in a file - gdb has more resilence in relation to process exit and suppresses warnings - Tidied up the tool definitions with comments and `freeze()` calls --- lib/ceedling/backtrace.gdb | 5 ++++ lib/ceedling/configurator.rb | 6 ++-- lib/ceedling/configurator_builder.rb | 2 +- lib/ceedling/configurator_setup.rb | 10 +++++-- lib/ceedling/constants.rb | 5 ++++ lib/ceedling/defaults.rb | 22 +++++++------- lib/ceedling/generator_test_results.rb | 2 +- .../generator_test_results_backtrace.rb | 29 ++++++++++++++++--- 8 files changed, 58 insertions(+), 23 deletions(-) create mode 100644 lib/ceedling/backtrace.gdb diff --git a/lib/ceedling/backtrace.gdb b/lib/ceedling/backtrace.gdb new file mode 100644 index 000000000..11ec159dc --- /dev/null +++ b/lib/ceedling/backtrace.gdb @@ -0,0 +1,5 @@ +if $_isvoid ($_exitcode) + call ((void(*)(int))fflush)(0) + backtrace + kill +end diff --git a/lib/ceedling/configurator.rb b/lib/ceedling/configurator.rb index 76eb90d50..d1eea9790 100644 --- a/lib/ceedling/configurator.rb +++ b/lib/ceedling/configurator.rb @@ -422,15 +422,15 @@ def validate_final(config) # Create constants and accessors (attached to this object) from given hash - def build(build_project_config, config, *keys) + def build(ceedling_lib_path, config, *keys) flattened_config = @configurator_builder.flattenify( config ) - @configurator_setup.build_project_config( build_project_config, flattened_config ) + @configurator_setup.build_project_config( ceedling_lib_path, flattened_config ) @configurator_setup.build_directory_structure( flattened_config ) # Copy Unity, CMock, CException into vendor directory within build directory - @configurator_setup.vendor_frameworks( flattened_config ) + @configurator_setup.vendor_frameworks_and_support_files( ceedling_lib_path, flattened_config ) @configurator_setup.build_project_collections( flattened_config ) diff --git a/lib/ceedling/configurator_builder.rb b/lib/ceedling/configurator_builder.rb index c044e7835..a873d0adb 100644 --- a/lib/ceedling/configurator_builder.rb +++ b/lib/ceedling/configurator_builder.rb @@ -146,7 +146,7 @@ def set_build_paths(in_hash) [:project_release_build_output_path, File.join(project_build_release_root, 'out'), in_hash[:project_release_build] ], [:project_release_dependencies_path, File.join(project_build_release_root, 'dependencies'), in_hash[:project_release_build] ], - [:project_log_path, File.join(in_hash[:project_build_root], 'logs'), true ], + [:project_log_path, File.join(in_hash[:project_build_root], 'logs'), true ], [:project_test_preprocess_includes_path, File.join(project_build_tests_root, 'preprocess/includes'), in_hash[:project_use_test_preprocessor] ], [:project_test_preprocess_files_path, File.join(project_build_tests_root, 'preprocess/files'), in_hash[:project_use_test_preprocessor] ], diff --git a/lib/ceedling/configurator_setup.rb b/lib/ceedling/configurator_setup.rb index a61fd65b8..92d84d7fb 100644 --- a/lib/ceedling/configurator_setup.rb +++ b/lib/ceedling/configurator_setup.rb @@ -49,7 +49,7 @@ def build_directory_structure(flattened_config) end end - def vendor_frameworks(flattened_config) + def vendor_frameworks_and_support_files(ceedling_lib_path, flattened_config) # Copy Unity C files into build/vendor directory structure @file_wrapper.cp_r( # '/.' to cause cp_r to copy directory contents @@ -70,10 +70,16 @@ def vendor_frameworks(flattened_config) File.join( flattened_config[:cexception_vendor_path], CEXCEPTION_LIB_PATH, '/.' ), flattened_config[:project_build_vendor_cexception_path] ) if flattened_config[:project_use_exceptions] + + # Copy backtrace debugging script into build/test directory structure + @file_wrapper.cp_r( + File.join( ceedling_lib_path, BACKTRACE_GDB_SCRIPT_FILE ), + flattened_config[:project_build_tests_root] + ) if flattened_config[:project_use_backtrace] == :gdb end def build_project_collections(flattened_config) - ### iterate through all entries in paths section and expand any & all globs to actual paths + # Iterate through all entries in paths section and expand any & all globs to actual paths flattened_config.merge!( @configurator_builder.expand_all_path_globs( flattened_config ) ) flattened_config.merge!( @configurator_builder.collect_vendor_paths( flattened_config ) ) diff --git a/lib/ceedling/constants.rb b/lib/ceedling/constants.rb index 858063b6f..2e1fef56e 100644 --- a/lib/ceedling/constants.rb +++ b/lib/ceedling/constants.rb @@ -65,6 +65,9 @@ class StdErrRedirect TCSH = :tcsh end +# Escaped newline literal (literally double-slash-n) for "encoding" multiline strings as single string +NEWLINE_TOKEN = '\\n' + DEFAULT_PROJECT_FILENAME = 'project.yml' GENERATED_DIR_PATH = [['vendor', 'ceedling'], 'src', "test", ['test', 'support'], 'build'].each{|p| File.join(*p)} @@ -101,6 +104,8 @@ class StdErrRedirect DEFAULT_CEEDLING_LOGFILE = 'ceedling.log' +BACKTRACE_GDB_SCRIPT_FILE = 'backtrace.gdb' + INPUT_CONFIGURATION_CACHE_FILE = 'input.yml' unless defined?(INPUT_CONFIGURATION_CACHE_FILE) # input configuration file dump DEFINES_DEPENDENCY_CACHE_FILE = 'defines_dependency.yml' unless defined?(DEFINES_DEPENDENCY_CACHE_FILE) # preprocessor definitions for files diff --git a/lib/ceedling/defaults.rb b/lib/ceedling/defaults.rb index 7e78c6800..115d2bcdb 100644 --- a/lib/ceedling/defaults.rb +++ b/lib/ceedling/defaults.rb @@ -64,20 +64,18 @@ } DEFAULT_TEST_FIXTURE_TOOL = { - # Unity test runner executable - :executable => '${1}'.freeze, + :executable => '${1}'.freeze, # Unity test runner executable :name => 'default_test_fixture'.freeze, :optional => false.freeze, :arguments => [].freeze } DEFAULT_TEST_FIXTURE_SIMPLE_BACKTRACE_TOOL = { - # Unity test runner executable - :executable => '${1}'.freeze, + :executable => '${1}'.freeze, # Unity test runner executable :name => 'default_test_fixture_simple_backtrace'.freeze, :optional => false.freeze, :arguments => [ - '-n ${2}' # Exact test case name matching flag + '-n ${2}'.freeze # Exact test case name matching flag ].freeze } @@ -240,13 +238,13 @@ # (Don't break a build if `gdb` is unavailable but backtrace does not require it.) :optional => true.freeze, :arguments => [ - '-q', - '--eval-command run', - '--eval-command backtrace', - '--batch', - '--args', - '${1}', - '-n ${2}' + '-q'.freeze, + '--batch'.freeze, + '--eval-command run'.freeze, + "--command \"${1}\"".freeze, # Debug script file to run + '--args'.freeze, + '${2}'.freeze, # Test executable + '-n ${3}'.freeze # Exact test case name matching flag ].freeze } diff --git a/lib/ceedling/generator_test_results.rb b/lib/ceedling/generator_test_results.rb index 8b3398f31..8331e4979 100644 --- a/lib/ceedling/generator_test_results.rb +++ b/lib/ceedling/generator_test_results.rb @@ -257,7 +257,7 @@ def extract_line_elements(executable, line, filename) :test => elements[1], :line => elements[0].to_i, # Decode any multline strings - :message => message.nil? ? nil : message.gsub( '\n', "\n" ), + :message => message.nil? ? nil : message.gsub( NEWLINE_TOKEN, "\n" ), :unity_test_time => unity_test_time } diff --git a/lib/ceedling/generator_test_results_backtrace.rb b/lib/ceedling/generator_test_results_backtrace.rb index f8ea2cf28..a4aad5f19 100644 --- a/lib/ceedling/generator_test_results_backtrace.rb +++ b/lib/ceedling/generator_test_results_backtrace.rb @@ -76,6 +76,8 @@ def do_simple(filename, executable, shell_result, test_cases) end def do_gdb(filename, executable, shell_result, test_cases) + gdb_script_filepath = File.join( @configurator.project_build_tests_root, BACKTRACE_GDB_SCRIPT_FILE ) + # Clean stats tracker test_case_results = @RESULTS_COLLECTOR.new( passed:0, failed:0, ignored:0, output:[] ) @@ -87,6 +89,7 @@ def do_gdb(filename, executable, shell_result, test_cases) # Build the test fixture to run with our test case of interest command = @tool_executor.build_command_line( @configurator.tools_backtrace_reporter, [], + gdb_script_filepath, executable, test_case[:test] ) @@ -133,8 +136,8 @@ def do_gdb(filename, executable, shell_result, test_cases) # Unity’s test executable output is line oriented. # Multi-line output is not possible (it looks like random `printf()` statements to the results parser) - # "Encode" actual newlines as literal "\n"s (slash-n) to be handled by the test results parser. - test_output = crash_report.gsub( "\n", '\n' ) + # "Encode" newlines in multiline string to be handled by the test results parser. + test_output = crash_report.gsub( "\n", NEWLINE_TOKEN ) test_output = "#{filename}:#{line_number}:#{test_case[:test]}:FAIL: Test case crashed >> #{test_output}" @@ -164,19 +167,36 @@ def do_gdb(filename, executable, shell_result, test_cases) private def filter_gdb_test_report( report, test_case, filename ) + # Sample `gdb` backtrace output + # ============================= + # [Thread debugging using libthread_db enabled] + # Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". + # + # Program received signal SIGSEGV, Segmentation fault. + # 0x0000555999f661fb in testCrash () at test/TestUsartModel.c:40 + # 40 uint32_t i = *nullptr; + # #0 0x0000555999f661fb in testCrash () at test/TestUsartModel.c:40 + # #1 0x0000555999f674de in run_test (func=0x555999f661e7 , name=0x555999f6b2e0 "testCrash", line_num=37) at build/test/runners/TestUsartModel_runner.c:76 + # #2 0x0000555999f6766b in main (argc=3, argv=0x7fff917e2c98) at build/test/runners/TestUsartModel_runner.c:117 + lines = report.split( "\n" ) report_start_index = 0 report_end_index = 0 - # Find line before last occurrence of `() at ` + # Find line preceding last occurrence of ` () at `; + # it is the offending line of code. + # We don't need the rest of the call trace -- it's just from the runner + # up to the crashed test case. lines.each_with_index do |line, index| if line =~ /#{test_case}.+at.+#{filename}/ report_end_index = (index - 1) unless (index == 0) end end - # Work up the report to find the top of the containing text block + # Work back up from end index to find top line of the containing text block. + # Go until we find a blank line; then increment the index back down a line. + # This lops off any unneeded preamble. report_end_index.downto(0).to_a().each do |index| if lines[index].empty? # Look for a blank line and adjust index to previous line of text @@ -187,6 +207,7 @@ def filter_gdb_test_report( report, test_case, filename ) length = (report_end_index - report_start_index) + 1 + # Reconstitute the report from the extracted lines return lines[report_start_index, length].join( "\n" ) end