Rust in Everything
基于 embassy-time-driver 0.2.2

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(bin nodriver_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 / wasm feature、HAL 的 time-driver-*,或自己的 time_driver_impl!。
  • 没有驱动时是链接错误,指向 embassy-time-driver 的 lib.rs:149;只有程序真的用到时间才报,所以可能在加第一个 Timer 时才出现。
  • stm32 开 time-driver-*;QEMU 里 TIM 没有模型,HAL 的驱动链接得过但时间不走。

💬 关联讨论

发起讨论
加载中...