coverage: replace coverage support with libjade coverage
What changed, and why it matters
This commit is a straightforward maintenance change: it removes the old firmware-level gcov code-coverage support and switches to a new libjade-based coverage workflow. There is no security bug being fixed here, and no new attack path is introduced. The only user-visible change is that the 'GCOV' field is no longer reported in the get_version_info RPC response.
No security action required. Treat as normal refactoring/coverage tooling update.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch removes CONFIG_APPTRACE_GCOV_ENABLE compile options, the debug_gcov_dump RPC handler, the GCOV key from build_version_info_reply, and associated Python helpers and shell scripts. It adds a GitLab CI job for libjade coverage and hardcodes gcov_dump=False in the OTA script. The changes are purely build/test infrastructure and do not alter cryptographic, wallet, or communication security logic.
Changed components
main/versioninfo.cmain/wire.cmain/CMakeLists.txtjadepy/jade.pyjade_ota.pytest_jade.pytools/gcov/gcovr_analyzer.pytools/gcov/generate_report.shgitlab/test_libjade.ymlInspect captured patch +22 / −98
diff --git a/gitlab/test_libjade.yml b/gitlab/test_libjade.yml
index 715497a..b639d20 100644
--- a/gitlab/test_libjade.yml
+++ b/gitlab/test_libjade.yml
@@ -38,3 +38,19 @@ test_libjade_sanitize:
name: libjade_serial_log
paths:
- daemon.log
+
+test_libjade_coverage:
+ extends: .libjade_test_template
+ stage: test
+ when: manual
+ script:
+ - ./libjade/make_libjade.sh Debug --gui --coverage
+ - ./libjade/coverage.sh clean
+ - export LD_LIBRARY_PATH=$PWD/build_linux/libjade
+ - python ./test_jade.py --log CRITICAL --libjade
+ - ./libjade/coverage.sh
+ artifacts:
+ expire_in: 2 days
+ name: libjade_coverage
+ paths:
+ - build_linux/lcov
diff --git a/jade_ota.py b/jade_ota.py
index 2d4c75e..a3c84f2 100755
--- a/jade_ota.py
+++ b/jade_ota.py
@@ -360,7 +360,7 @@ def ota(args, jade, info, extended_replies):
result = jade.ota_update(fwcompressed, fwlength, chunksize, fwhash,
patchlen=patchlen, cb=_log_progress,
extended_replies=extended_replies,
- gcov_dump=info.get('GCOV', False))
+ gcov_dump=False)
assert result is True
logger.info(f'Total ota time in secs: {time.time() - start_time}')
diff --git a/jadepy/jade.py b/jadepy/jade.py
index eb29b22..bda8651 100644
--- a/jadepy/jade.py
+++ b/jadepy/jade.py
@@ -590,9 +590,6 @@ class JadeAPI:
if (cb):
cb(written, cmplen, result if have_extended_reply else None)
- if gcov_dump:
- self.run_remote_gcov_dump()
-
# All binary data uploaded
return self._jadeRpc('ota_complete')
@@ -611,19 +608,14 @@ class JadeAPI:
def run_remote_gcov_dump(self):
"""
- RPC call to run in-built gcov-dump.
- NOTE: Only available in a DEBUG build of the firmware.
+ Deprecated, has no effect.
Returns
-------
bool
Always True.
"""
- result = self._jadeRpc('debug_gcov_dump', long_timeout=True)
- time.sleep(0.5)
- generate_dump()
- time.sleep(2)
- return result
+ return True
def capture_image_data(self, check_qr=False):
"""
diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt
index 571d6da..f2a49f7 100755
--- a/main/CMakeLists.txt
+++ b/main/CMakeLists.txt
@@ -82,8 +82,5 @@ if(CONFIG_AMALGAMATED_BUILD)
endif()
target_link_libraries(${COMPONENT_TARGET} "-u custom_app_desc")
-if(CONFIG_APPTRACE_GCOV_ENABLE)
- target_compile_options(${COMPONENT_LIB} PRIVATE --coverage)
-endif()
list(APPEND link_options "-Wl,--wrap=abort")
idf_build_set_property(LINK_OPTIONS "${link_options}" APPEND)
diff --git a/main/versioninfo.c b/main/versioninfo.c
index 664d26c..adca6cc 100644
--- a/main/versioninfo.c
+++ b/main/versioninfo.c
@@ -32,7 +32,7 @@ void build_version_info_reply(const void* ctx, CborEncoder* container)
const jade_msg_source_t* const source = (const jade_msg_source_t*)ctx;
#ifdef CONFIG_DEBUG_MODE
- const uint8_t num_version_fields = 23;
+ const uint8_t num_version_fields = 22;
#else
const uint8_t num_version_fields = 15;
#endif
@@ -133,11 +133,6 @@ void build_version_info_reply(const void* ctx, CborEncoder* container)
add_uint_to_map(&map_encoder, "JADE_FREE_SPIRAM", heap_caps_get_free_size(MALLOC_CAP_DEFAULT | MALLOC_CAP_SPIRAM));
add_uint_to_map(
&map_encoder, "JADE_LARGEST_SPIRAM", heap_caps_get_largest_free_block(MALLOC_CAP_DEFAULT | MALLOC_CAP_SPIRAM));
-#ifdef CONFIG_APPTRACE_GCOV_ENABLE
- add_boolean_to_map(&map_encoder, "GCOV", true);
-#else
- add_boolean_to_map(&map_encoder, "GCOV", false);
-#endif
#endif // CONFIG_DEBUG_MODE
cberr = cbor_encoder_close_container(container, &map_encoder);
diff --git a/main/wire.c b/main/wire.c
index 5422c23..d0ecc24 100644
--- a/main/wire.c
+++ b/main/wire.c
@@ -7,9 +7,6 @@
#include <cbor.h>
#include <esp_ota_ops.h>
#include <esp_system.h>
-#if defined(CONFIG_IDF_TARGET_ESP32S3) && defined(CONFIG_DEBUG_MODE) && defined(CONFIG_APPTRACE_GCOV_ENABLE)
-#include <esp_app_trace.h>
-#endif
#include "idletimer.h"
#include "jade_assert.h"
@@ -44,9 +41,6 @@ static const TickType_t TIMEOUT_TICKS = 2000 / portTICK_PERIOD_MS;
// Some messages we handle immediately in this task
static const char PING[] = { 'p', 'i', 'n', 'g' };
static const char VERINFO[] = { 'g', 'e', 't', '_', 'v', 'e', 'r', 's', 'i', 'o', 'n', '_', 'i', 'n', 'f', 'o' };
-#if defined(CONFIG_IDF_TARGET_ESP32S3) && defined(CONFIG_DEBUG_MODE) && defined(CONFIG_APPTRACE_GCOV_ENABLE)
-static const char DEBUG_GCOV_DUMP[] = { 'd', 'e', 'b', 'u', 'g', '_', 'g', 'c', 'o', 'v', '_', 'd', 'u', 'm', 'p' };
-#endif
static bool handleImmediateMessage(cbor_msg_t* ctx)
{
@@ -76,14 +70,6 @@ static bool handleImmediateMessage(cbor_msg_t* ctx)
jade_process_reply_to_message_result(*ctx, buf, sizeof(buf), &ctx->source, build_version_info_reply);
return true;
}
-#if defined(CONFIG_IDF_TARGET_ESP32S3) && defined(CONFIG_DEBUG_MODE) && defined(CONFIG_APPTRACE_GCOV_ENABLE)
- } else if (method_len == sizeof(DEBUG_GCOV_DUMP) && !strncmp(method, DEBUG_GCOV_DUMP, method_len)) {
- uint8_t buf[64];
- const bool ok = true;
- jade_process_reply_to_message_result(*ctx, buf, sizeof(buf), &ok, cbor_result_boolean_cb);
- esp_gcov_dump();
- return true;
-#endif
}
}
return false;
diff --git a/test_jade.py b/test_jade.py
index 32356a8..b91546d 100644
--- a/test_jade.py
+++ b/test_jade.py
@@ -178,7 +178,7 @@ PINSERVER_DEFAULT_URL = 'https://j8d.io'
PINSERVER_DEFAULT_ONION = 'http://mrrxtq6tjpbnbm7vh5jt6mpjctn7ggyfy5wegvbeff3x7jrznqawlmid.onion'
# The number of values expected back in version info
-NUM_VALUES_VERINFO = 23
+NUM_VALUES_VERINFO = 22
ESP32S3_CHIP_BOARDS = ['JADE_V2', 'JADE_V2C', 'TTGO_TDISPLAYS3', 'TTGO_TDISPLAYS3PROCAMERA',
'M5CORES3']
@@ -2531,7 +2531,7 @@ def check_mem_stats(startinfo, endinfo, has_psram, has_ble, strict=True):
if breaches:
logger.error(f'Memory limit breaches: {breaches}')
- assert endinfo['GCOV'] or not strict
+ assert not strict
# Helper to verify a signature - handles checking an Anti-Exfil signature
@@ -4122,9 +4122,6 @@ def run_all_jade_tests(info):
with JadeAPI.create_serial(args.serialport,
timeout=args.serialtimeout) as jade:
run_jade_tests(jade, isble=False)
- # 1.1 Code coverage
- if info['GCOV'] and (args.skipble or info['JADE_CONFIG'] != 'BLE'):
- jade.run_remote_gcov_dump()
# 2. Test over BLE connection
if not args.skipble:
@@ -4137,10 +4134,6 @@ def run_all_jade_tests(info):
# 3. If testing both interfaces, test cannot connect 'other' when one in use
if not args.skipserial:
mixed_sources_test(args.serialport, bleid)
-
- # 3.1 Code coverage
- if info['GCOV']:
- jade.run_remote_gcov_dump()
else:
msg = 'Skipping BLE tests - not enabled on the hardware'
logger.warning(msg)
diff --git a/tools/gcov/gcovr_analyzer.py b/tools/gcov/gcovr_analyzer.py
deleted file mode 100755
index 7eb3738..0000000
--- a/tools/gcov/gcovr_analyzer.py
+++ /dev/null
@@ -1,36 +0,0 @@
-#!/usr/bin/env python3
-
-import json
-import sys
-from collections import defaultdict
-
-def parse_gcovr_json(file_path):
- with open(file_path, 'r') as file:
- data = json.load(file)
-
- line_counts = defaultdict(int)
-
- for file_data in data['files']:
- file_name = file_data['file']
- for line in file_data['lines']:
- if not line.get('gcovr/noncode', False):
- line_number = line['line_number']
- execution_count = line['count']
- line_counts[(file_name, line_number)] += execution_count
-
- return line_counts
-
-def main(file_path):
- line_counts = parse_gcovr_json(file_path)
- sorted_lines = sorted(line_counts.items(), key=lambda item: item[1], reverse=True)
-
- for (file_name, line_number), count in sorted_lines:
- print(f'{file_name} Line {line_number}: {count}')
-
-if __name__ == '__main__':
- if len(sys.argv) != 2:
- print('Usage: python script.py <coverage_json_file>')
- sys.exit(1)
-
- file_path = sys.argv[1]
- main(file_path)
diff --git a/tools/gcov/generate_report.sh b/tools/gcov/generate_report.sh
deleted file mode 100755
index caf1a87..0000000
--- a/tools/gcov/generate_report.sh
+++ /dev/null
@@ -1,19 +0,0 @@
-#!/bin/bash
-set -eo pipefail
-
-export gcov_root_path=${PWD}
-
-cd build
-mkdir -p ${gcov_root_path}/build/coverage_report/html
-# we can't run idf default gcov target because we need --gcov-ignore-parse-errors and excludes (FIXME: maybe they can be passed in as an argument)
-
-# produce nice html with code coverage
-gcovr -r ${gcov_root_path} --gcov-executable xtensa-esp32s3-elf-gcov --exclude "${gcov_root_path}/managed_components" --gcov-ignore-parse-errors -s --html-details ${gcov_root_path}/build/coverage_report/html/index.html
-
-# produce a json file useful to analyze which lines are called the most (FIXME: it would be nice it mentioned functions instead)
-gcovr -r ${gcov_root_path} --gcov-executable xtensa-esp32s3-elf-gcov --exclude "${gcov_root_path}/managed_components" --gcov-ignore-parse-errors -s --json -o ${gcov_root_path}/build/coverage_report/coverage.json
-
-${gcov_root_path}/tools/gcov/gcovr_analyzer.py ${gcov_root_path}/build/coverage_report/coverage.json > ${gcov_root_path}/build/coverage_report/analysys.txt
-
-echo "You can open the generated report at ${gcov_root_path}/build/coverage_report/html/index.html"
-unset gcov_root_path
Why this scored 14/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.