From 5b531b4e60b6410789382495bf76577adfa871f7 Mon Sep 17 00:00:00 2001 From: Zach Vorhies Date: Thu, 8 Oct 2026 21:01:45 -0700 Subject: [PATCH] fix(deploy): skip the ESP32-S3 QEMU ADC patch when adc_hw_calibration is not linked The QEMU image patch required adc_hw_calibration and failed the deploy when the firmware did not link libesp_adc's calibration constructor. FastLED no longer pulls that object into every sketch (FastLED#4796), so ordinary sketches have no boot ADC calibration for QEMU to hang on. Treat a missing symbol as nothing to patch. Adds a unit test with a synthetic ELF. Local-Gate: v1 tree=f23c056ba917d2a1be6917c6c68bb144e116418f secs=1885 lanes=linux-minimal:run,dylint:run Ci-Attestation: {"at":1791523520,"gate":"general/all/ubuntu-ci-guards","host":"linux-x86_64","key":"8a08cb746cc46d9a5c901b73513bdf5f96da1a946b3898df09aeb314e253db52","lane":"linux-minimal","parents":["c1fc4d49823f2f72da1b5092bc1834fadcdfa4d4"],"secs":1018,"stamp":"7eda755ba8ef0c51e5d7f2cd986e2dc0","tree":"f23c056ba917d2a1be6917c6c68bb144e116418f","v":1,"via":"run"} Ci-Attestation: {"at":1791523520,"gate":"rust/x86_64-unknown-linux-gnu/workspace-clippy","host":"linux-x86_64","key":"8a08cb746cc46d9a5c901b73513bdf5f96da1a946b3898df09aeb314e253db52","lane":"linux-minimal","parents":["c1fc4d49823f2f72da1b5092bc1834fadcdfa4d4"],"secs":1018,"stamp":"de39fe1ac5f1b3e430d52ad87f8ffaec","tree":"f23c056ba917d2a1be6917c6c68bb144e116418f","v":1,"via":"run"} Ci-Attestation: {"at":1791523520,"gate":"rust/x86_64-unknown-linux-gnu/workspace-test","host":"linux-x86_64","key":"8a08cb746cc46d9a5c901b73513bdf5f96da1a946b3898df09aeb314e253db52","lane":"linux-minimal","parents":["c1fc4d49823f2f72da1b5092bc1834fadcdfa4d4"],"secs":1018,"stamp":"912b80a1cbe7464a5be4e58d0e214b7c","tree":"f23c056ba917d2a1be6917c6c68bb144e116418f","v":1,"via":"run"} Ci-Attestation: {"at":1791523520,"gate":"rust/x86_64-unknown-linux-gnu/python-facade-test","host":"linux-x86_64","key":"8a08cb746cc46d9a5c901b73513bdf5f96da1a946b3898df09aeb314e253db52","lane":"linux-minimal","parents":["c1fc4d49823f2f72da1b5092bc1834fadcdfa4d4"],"secs":1018,"stamp":"4ca61c9e0a55d18a73b9212ccd6b1b4e","tree":"f23c056ba917d2a1be6917c6c68bb144e116418f","v":1,"via":"run"} Ci-Attestation: {"at":1791523520,"gate":"general/all/dylint-policy","host":"linux-x86_64","key":"a75dfed9ab41b77fec42a4b644038abb19eb975bb9fac7e2c4775d1b00f94349","lane":"dylint","parents":["c1fc4d49823f2f72da1b5092bc1834fadcdfa4d4"],"secs":866,"stamp":"3a9b6039a039e8f3819dabb21f056bf8","tree":"f23c056ba917d2a1be6917c6c68bb144e116418f","v":1,"via":"run"} Ci-Attestation: {"at":1791523520,"gate":"rust/x86_64-unknown-linux-gnu/dylint-library-check","host":"linux-x86_64","key":"a75dfed9ab41b77fec42a4b644038abb19eb975bb9fac7e2c4775d1b00f94349","lane":"dylint","parents":["c1fc4d49823f2f72da1b5092bc1834fadcdfa4d4"],"secs":866,"stamp":"42598024ac021408bac033da23c18200","tree":"f23c056ba917d2a1be6917c6c68bb144e116418f","v":1,"via":"run"} Ci-Attestation: {"at":1791523520,"gate":"rust/x86_64-unknown-linux-gnu/workspace-dylint","host":"linux-x86_64","key":"a75dfed9ab41b77fec42a4b644038abb19eb975bb9fac7e2c4775d1b00f94349","lane":"dylint","parents":["c1fc4d49823f2f72da1b5092bc1834fadcdfa4d4"],"secs":866,"stamp":"b50e159900c54d2d7c6f468e0e218b01","tree":"f23c056ba917d2a1be6917c6c68bb144e116418f","v":1,"via":"run"} --- crates/fbuild-deploy/src/esp32/image.rs | 24 +++++++++++++ crates/fbuild-deploy/src/esp32/tests.rs | 47 +++++++++++++++++++++++++ 2 files changed, 71 insertions(+) diff --git a/crates/fbuild-deploy/src/esp32/image.rs b/crates/fbuild-deploy/src/esp32/image.rs index caa316ec1..1b414b53f 100644 --- a/crates/fbuild-deploy/src/esp32/image.rs +++ b/crates/fbuild-deploy/src/esp32/image.rs @@ -24,6 +24,16 @@ pub(super) fn patch_qemu_esp32s3_adc_calibration( elf_path: &Path, firmware_offset: u64, ) -> Result<()> { + // The ADC calibration constructor is only linked when the firmware pulls + // in libesp_adc (e.g. it reads an analog pin). Without it there is no boot + // calibration for QEMU to hang on, so there is nothing to patch. + if !elf_has_symbol(elf_path, QEMU_ADC_CALIBRATION_SYMBOL)? { + tracing::info!( + "{} not linked; ESP32-S3 QEMU image needs no ADC calibration patch", + QEMU_ADC_CALIBRATION_SYMBOL + ); + return Ok(()); + } let symbol_addr = resolve_local_elf_symbol_address(elf_path, QEMU_ADC_CALIBRATION_SYMBOL)?; let patch_addr = symbol_addr .checked_add(QEMU_ADC_CALIBRATION_PATCH_OFFSET) @@ -52,6 +62,20 @@ pub(super) fn patch_qemu_esp32s3_adc_calibration( Ok(()) } +pub(super) fn elf_has_symbol(elf_path: &Path, symbol_name: &str) -> Result { + let bytes = std::fs::read(elf_path)?; + let object = object::File::parse(bytes.as_slice()).map_err(|e| { + fbuild_core::FbuildError::DeployFailed(format!( + "failed to parse ELF {}: {}", + elf_path.display(), + e + )) + })?; + Ok(object + .symbols() + .any(|symbol| symbol.name().ok() == Some(symbol_name))) +} + fn resolve_local_elf_symbol_address(elf_path: &Path, symbol_name: &str) -> Result { let bytes = std::fs::read(elf_path)?; let object = object::File::parse(bytes.as_slice()).map_err(|e| { diff --git a/crates/fbuild-deploy/src/esp32/tests.rs b/crates/fbuild-deploy/src/esp32/tests.rs index 4fd2b2277..a57765a3c 100644 --- a/crates/fbuild-deploy/src/esp32/tests.rs +++ b/crates/fbuild-deploy/src/esp32/tests.rs @@ -895,3 +895,50 @@ async fn try_verify_deployment_real_esp32h2() { async fn try_verify_deployment_real_esp32p4() { run_verify_deployment_test("esp32p4", "0x2000", "ESP32P4_PORT", "ESP32P4_FIRMWARE").await; } + +fn write_test_elf(dir: &Path, with_adc_symbol: bool) -> std::path::PathBuf { + use object::write::{Object, Symbol, SymbolSection}; + use object::{Architecture, BinaryFormat, Endianness, SymbolFlags, SymbolKind, SymbolScope}; + let mut obj = Object::new(BinaryFormat::Elf, Architecture::Xtensa, Endianness::Little); + let name: &[u8] = if with_adc_symbol { + b"adc_hw_calibration" + } else { + b"app_main" + }; + obj.add_symbol(Symbol { + name: name.to_vec(), + value: 0x4200_0000, + size: 4, + kind: SymbolKind::Text, + scope: SymbolScope::Linkage, + weak: false, + section: SymbolSection::Absolute, + flags: SymbolFlags::None, + }); + let path = dir.join(if with_adc_symbol { + "with.elf" + } else { + "without.elf" + }); + std::fs::write(&path, obj.write().unwrap()).unwrap(); + path +} + +#[test] +fn qemu_adc_patch_is_skipped_when_calibration_not_linked() { + // Firmware that never links libesp_adc has no adc_hw_calibration + // constructor, so there is nothing for QEMU to hang on and nothing to + // patch: the deploy must succeed and leave the image untouched. + let dir = tempfile::tempdir().unwrap(); + let elf = write_test_elf(dir.path(), false); + let firmware = dir.path().join("firmware.bin"); + let flash = dir.path().join("flash.bin"); + std::fs::write(&firmware, [0u8; 16]).unwrap(); + std::fs::write(&flash, [0xFFu8; 64]).unwrap(); + super::image::patch_qemu_esp32s3_adc_calibration(&flash, &firmware, &elf, 0).unwrap(); + assert_eq!(std::fs::read(&flash).unwrap(), vec![0xFFu8; 64]); + assert!( + super::image::elf_has_symbol(&write_test_elf(dir.path(), true), "adc_hw_calibration") + .unwrap() + ); +}