moonbit FFI 的回调参数问题

我正在开发一个 moonbit 和 webview 的 binding。在绑定 webview_bind 函数时遇到了问题。

justjavac/moonbit-webview: MoonBit bindings for webview.

C API 定义:

extern void webview_bind(
    webview_t w, 
    const char *name, 
    void (*fn)(const char *id, const char *req, void *arg),
    void *arg
);

MoonBit ffi 定义:

pub extern "C" fn webview_bind(
  webview : Webview_t,
  name : Bytes,
  call_closure : FuncRef[(Bytes, Bytes, (Bytes, Bytes) -> Unit) -> Unit],
  closure : (Bytes, Bytes) -> Unit
) -> Int = "webview_bind"

pub fn Webview::bind(
  self : Webview,
  name : String,
  callback : (Bytes, Bytes) -> Unit
) -> Unit {
  self.callbacks.set(name, callback)
  let _ = webview_bind(
    self.handle,
    @ffi.to_cstr(name),
    fn(id, req, f) { f(id, req) },
    callback,
  )
}

我是仿照这文档里面的闭包章节写的。现在程序可以运行,但是执行绑定回调的时候直接 crash 了,堆缓冲区溢出(heap-buffer-overflow)。

==24892==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x127d1d1a67cc at pc 0x7ff718f7a758 bp 0x00795b7ef130 sp 0x00795b7ef138
READ of size 4 at 0x127d1d1a67cc thread T0
    #0 0x7ff718f7a757 in $$moonbitlang$core$builtin$Show$$Bytes$$output (D:\Code\moonbit-webview\target\native\release\build\examples\10_bind\10_bind.exe+0x14000a757)
    #1 0x7ff718f7b860 in $$moonbitlang$core$builtin$Show$$$default_impl$$to_string$0 (D:\Code\moonbit-webview\target\native\release\build\examples\10_bind\10_bind.exe+0x14000b860)
    #2 0x7ff718f742b2 in $moonbitlang$core$builtin$println$0 (D:\Code\moonbit-webview\target\native\release\build\examples\10_bind\10_bind.exe+0x1400042b2)
    #3 0x7ff718f7e723 in $_main$2a$$fn$1 (D:\Code\moonbit-webview\target\native\release\build\examples\10_bind\10_bind.exe+0x14000e723)
    #4 0x7ff718f71b28 in $$justjavac$webview$Webview$$bind$fn$7 (D:\Code\moonbit-webview\target\native\release\build\examples\10_bind\10_bind.exe+0x140001b28)
    #5 0x7fffbd1a5b3c  (D:\Code\moonbit-webview\lib\webview.dll+0x180005b3c)
    #6 0x7fffbd1a45dc  (D:\Code\moonbit-webview\lib\webview.dll+0x1800045dc)
    #7 0x7fffbd1a1018  (D:\Code\moonbit-webview\lib\webview.dll+0x180001018)
    #8 0x7fffd038d158  (C:\WINDOWS\System32\USER32.dll+0x18000d158)
    #9 0x7fffd038b151  (C:\WINDOWS\System32\USER32.dll+0x18000b151)
    #10 0x7fffbd1ac925  (D:\Code\moonbit-webview\lib\webview.dll+0x18000c925)
    #11 0x7fffbd1a1f5d  (D:\Code\moonbit-webview\lib\webview.dll+0x180001f5d)
    #12 0x7fffbd1ada22 in webview_run (D:\Code\moonbit-webview\lib\webview.dll+0x18000da22)
    #13 0x7ff718f71716 in $$justjavac$webview$Webview$$run (D:\Code\moonbit-webview\target\native\release\build\examples\10_bind\10_bind.exe+0x140001716)
    #14 0x7ff718f7efd2 in main (D:\Code\moonbit-webview\target\native\release\build\examples\10_bind\10_bind.exe+0x14000efd2)
    #15 0x7ff718f81e83 in invoke_main D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:78
    #16 0x7ff718f81e83 in __scrt_common_main_seh D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:288
    #17 0x7fffcf99e8d6  (C:\WINDOWS\System32\KERNEL32.DLL+0x18002e8d6)
    #18 0x7fffd0f9c34b  (C:\WINDOWS\SYSTEM32\ntdll.dll+0x18003c34b)

0x127d1d1a67cc is located 4 bytes before 48-byte region [0x127d1d1a67d0,0x127d1d1a6800)
allocated by thread T0 here:
    #0 0x7ffedc76d698 in malloc D:\a\_work\1\s\src\vctools\asan\llvm\compiler-rt\lib\asan\asan_malloc_win.cpp:134
    #1 0x7fffbd1ade9e in webview_version (D:\Code\moonbit-webview\lib\webview.dll+0x18000de9e)
    #2 0x7fffbd1a2885  (D:\Code\moonbit-webview\lib\webview.dll+0x180002885)
    #3 0x7fffbd1a5b0b  (D:\Code\moonbit-webview\lib\webview.dll+0x180005b0b)
    #4 0x7fffbd1a45dc  (D:\Code\moonbit-webview\lib\webview.dll+0x1800045dc)
    #5 0x7fffbd1a1018  (D:\Code\moonbit-webview\lib\webview.dll+0x180001018)
    #6 0x7fffd038d158  (C:\WINDOWS\System32\USER32.dll+0x18000d158)
    #7 0x7fffd038b151  (C:\WINDOWS\System32\USER32.dll+0x18000b151)
    #8 0x7fffbd1ac925  (D:\Code\moonbit-webview\lib\webview.dll+0x18000c925)
    #9 0x7fffbd1a1f5d  (D:\Code\moonbit-webview\lib\webview.dll+0x180001f5d)
    #10 0x7fffbd1ada22 in webview_run (D:\Code\moonbit-webview\lib\webview.dll+0x18000da22)
    #11 0x7ff718f71716 in $$justjavac$webview$Webview$$run (D:\Code\moonbit-webview\target\native\release\build\examples\10_bind\10_bind.exe+0x140001716)
    #12 0x7ff718f7efd2 in main (D:\Code\moonbit-webview\target\native\release\build\examples\10_bind\10_bind.exe+0x14000efd2)
    #13 0x7ff718f81e83 in invoke_main D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:78
    #14 0x7ff718f81e83 in __scrt_common_main_seh D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:288
    #15 0x7fffcf99e8d6  (C:\WINDOWS\System32\KERNEL32.DLL+0x18002e8d6)
    #16 0x7fffd0f9c34b  (C:\WINDOWS\SYSTEM32\ntdll.dll+0x18003c34b)

SUMMARY: AddressSanitizer: heap-buffer-overflow (D:\Code\moonbit-webview\target\native\release\build\examples\10_bind\10_bind.exe+0x14000a757) in $$moonbitlang$core$builtin$Show$$Bytes$$output
Shadow bytes around the buggy address:
  0x127d1d1a6500: fa fa fd fd fd fd fd fa fa fa fd fd fd fd fd fa
  0x127d1d1a6580: fa fa fd fd fd fd fd fa fa fa fd fd fd fd fd fa
  0x127d1d1a6600: fa fa fd fd fd fd fd fd fa fa fd fd fd fd fd fa
  0x127d1d1a6680: fa fa fd fd fd fd fd fd fa fa fd fd fd fd fd fd
  0x127d1d1a6700: fa fa fd fd fd fd fd fd fa fa 00 00 00 00 00 00
=>0x127d1d1a6780: fa fa 00 00 00 00 00 00 fa[fa]00 00 00 00 00 00
  0x127d1d1a6800: fa fa 00 00 00 00 00 fa fa fa fa fa fa fa fa fa
  0x127d1d1a6880: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x127d1d1a6900: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x127d1d1a6980: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x127d1d1a6a00: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
==24892==ABORTING

我看你仓库 CI 过了,这个问题还存在么?

这里 fn callback 的参数(id, req)按照我的猜想应该是 C 那边分配的?那这样的话你不能够直接在 MoonBit 这边绑定为 Bytes ,因为通过 malloc 分配出来的内存不会有 MoonBit 内存对象的对象头 ,而绑定为 Bytes 会让编译器认为这个是 MoonBit 中的对象,从而在执行回调的时候访问对象头造成访问越界。

解决方法是自己定义一个 extern type 来表示 C 那边分配的 const char * ,然后在 MoonBit 这边分配 Bytes 把内容拷贝到 MoonBit 的 Bytes 里来。

CI 过了因为这个编译没问题,运行也没问题。只有在执行回调的时候才会出问题。

多谢你的解答,我晚上再试一下