Undefined symbols: _embassy_time_now
主线位置:定时器主线的「
Timer把到期时刻和 waker 交给时间驱动」。embassy-time和驱动之间靠两个链接期符号接上,机制见时间驱动;本页讲这两个符号没人定义时的样子。
flowchart TB
t["Timer::after"] --> n["driver::now"]
n --> sym["_embassy_time_now"]
sym --> q{"谁定义了它"}
q -->|std feature| a["driver_std.rs"]
q -->|"time-driver-*"| b["stm32 gp16.rs"]
q -->|自己写| c["time_driver_impl!"]
q -->|没有| err["链接失败"]
实验 crate exp-embassy/pita-nodriver(std)和 exp-embassy/pita-qemu、pita-tim(thumbv7em-none-eabihf,STM32L475VG);M2 Max、macOS 27、rustc 1.99.0。
现象
std 上,embassy-time 不开任何 feature,执行器开 platform-std:
#[embassy_executor::main]
async fn main(_spawner: Spawner) {
Timer::after_millis(100).await;
println!("{}", Instant::now());
std::process::exit(0);
}
cargo build(debug)在链接时失败,cc 那一长行命令省略:
error: linking with `cc` failed: exit status: 1
...
= note: Undefined symbols for architecture arm64:
"__embassy_time_now", referenced from:
__RNvCs4QyoBtEmjrB_19embassy_time_driver3nowCsgUL373qTsvn_13pita_nodriver in pita_nodriver-e56959640dfd1dda.4z1ok7ya5feth613vxbx0tqf7.1d9hzex.rcgu.o
__RNvCs4QyoBtEmjrB_19embassy_time_driver3nowCsjiL2pIPuoyS_12embassy_time in libembassy_time-9c17b3c36aeb4e35.rlib[4](embassy_time-9c17b3c36aeb4e35.embassy_time.e0d42f82d97aebd4-cgu.0.rcgu.o)
"__embassy_time_schedule_wake", referenced from:
__RNvCs4QyoBtEmjrB_19embassy_time_driver13schedule_wakeCsjiL2pIPuoyS_12embassy_time in libembassy_time-9c17b3c36aeb4e35.rlib[4](embassy_time-9c17b3c36aeb4e35.embassy_time.e0d42f82d97aebd4-cgu.0.rcgu.o)
ld: symbol(s) not found for architecture arm64
在芯片上更常见的写法是 embassy-stm32 忘了开 time-driver-*。pita-qemu 的 embassy-stm32 只开了 stm32l475vg、memory-x、exti、unstable-pac,bin nodriver 调 embassy_stm32::init 后等一个 Timer,cargo build --release 交给 rust-lld:
= note: rust-lld: error: undefined symbol: _embassy_time_now
>>> referenced by lib.rs:149 (~/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/embassy-time-driver-0.2.2/src/lib.rs:149)
>>> ~/exp-embassy/target-pita-qemu/thumbv7em-none-eabihf/release/deps/nodriver-b830fc49f2c5d44a.nodriver.8ea63248086490dd-cgu.0.rcgu.o:(<embassy_executor::raw::TaskStorage<nodriver::____embassy_main_task::____embassy_main_task_inner_function::{closure#0}>>::poll)
...
rust-lld: error: undefined symbol: _embassy_time_schedule_wake
>>> referenced by lib.rs:155 (~/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/embassy-time-driver-0.2.2/src/lib.rs:155)
- macOS 的 Mach-O 给符号多加一个下划线,所以
ld说__embassy_time_now,rust-lld 说_embassy_time_now,是同一个符号。 - 报错指向
embassy-time-driver的lib.rs:149/:155,那是now()和schedule_wake()的函数体,不是用户代码。用户代码只出现在「referenced by」后面的目标文件名里。 - 同一个依赖图、同一套 feature,只是不用
Timer和Instant(binnodriver_unused),链接和运行都正常,QEMU 上打印no timer used。链接器只在有人引用时才去找这两个符号,所以这个错误往往在第一次加Timer的那天才出现,或者在某个依赖升级、开始在内部调embassy-time的那天出现。
出处
embassy-time-driver 只声明,不定义:
unsafe extern "Rust" {
fn _embassy_time_now() -> u64;
fn _embassy_time_schedule_wake(at: u64, waker: &Waker);
}
/// See [`Driver::now`]
#[inline]
pub fn now() -> u64 {
unsafe { _embassy_time_now() }
}
定义只来自 time_driver_impl! 这个宏,本卷讲到的 crate 里调用它的只有五处:embassy-time 的 driver_std.rs、driver_mock.rs、driver_wasm.rs(分别由 std、mock-driver、wasm feature 打开),以及 embassy-stm32 的 gp16.rs 和 lptim.rs(由 time-driver-* 打开)。都没开,就没有定义。为什么用链接期符号而不是泛型参数,见时间驱动的「链接期符号:没有泛型参数」。
修法
std:给 embassy-time 开 std(pita-nodriver 的 fix feature 就是 ["embassy-time/std"]):
embassy-time = { version = "=0.5.1", features = ["std"] }
105020 ticks
std 驱动的 tick 是 1 µs,100 ms 的 Timer 之后打印 105020 ticks。
stm32:开一个 time-driver-*。pita-tim 和 pita-qemu 只差 embassy-stm32 多一个 time-driver-any,同一份 nodriver.rs 链接通过。time-driver-any 让 HAL 自己挑一个定时器,time-driver-tim2 这类 feature 指定一个,选法见时间驱动的「选哪个定时器」。
这个 ELF 在 QEMU 里跑不出结果:L475 的 TIM 在 QEMU 里没有模型,now() 永远是 0,Timer::after_millis(100) 永远不到期,5 秒后被 runq 杀掉,什么也没打印。QEMU 上的实验因此都用模板里自己写的 SysTick 驱动,那是第三种修法:在二进制 crate 里写一个 impl Driver,再用 time_driver_impl! 导出。
怎么确认没中招
- 看依赖图里谁在提供驱动:
cargo tree -e features -i embassy-time-driver,找embassy-time feature "std"、embassy-stm32 feature "time-driver-..."这样的分支;一个都没有,又有人用Timer,就会撞上。 - 只想用
Instant/Duration做换算、不需要真实时间的库,也不能调Instant::now(),否则把这个要求转嫁给了所有下游。 - 链接通过后用
llvm-nm确认符号确实来自期望的那个驱动:llvm-nm -C <elf> | grep embassy_time_now,大写T表示已定义。两个驱动同时存在是另一个坑,见 symbol _embassy_time_now is already defined。
小结
embassy-time只声明_embassy_time_now和_embassy_time_schedule_wake,定义来自std/mock-driver/wasmfeature、HAL 的time-driver-*,或自己的time_driver_impl!。- 没有驱动时是链接错误,指向
embassy-time-driver的lib.rs:149;只有程序真的用到时间才报,所以可能在加第一个Timer时才出现。 - stm32 开
time-driver-*;QEMU 里 TIM 没有模型,HAL 的驱动链接得过但时间不走。